So I was working on pulling photos from user's Facebook album and for some reason NSDictionary doesn't throw an exception when nil is inserted.
I've tested it on a real device and on iOS simulator (both iOS 7).
Maybe debugger crashes, maybe it's Facebook SDK bug.
Anyway, I'm ready to listen to your opinion.
UPDATE: One more thing I forgot to mention. In debugger, after inserting nil object in dictionary, debugger itself continues program execution. And I'm not even pressing the buttong "Continue program execution".
Here's the snippet
- (void)downloadAlbumsWithCompletionBlock:(void (^)(NSArray *albums, NSError *error))completionBlock;
{
[FBRequestConnection startWithGraphPath:#"/me/albums"
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (error)
{
NSLog(#"%#", error.localizedDescription);
completionBlock(nil, error);
}
else
{
NSMutableArray *data = [result objectForKey:#"data"];
NSMutableArray *albums = [NSMutableArray new];
for (NSMutableDictionary *dict in data)
{
NSString *albumID = dict[#"id"];
NSString *coverPhotoID = dict[#"cover_photo"];
// coverPhotoID gets nil and then successfully gets put in NSDictionary
// You can just write "NSString *coverPhotoID = nil;".
NSString *description = dict[#"description"];
NSString *name = dict[#"name"];
NSDictionary *album = #{#"album_id": albumID,
#"album_cover_photo_id": coverPhotoID,
#"album_description": description,
#"album_name": name };
// Here an exception should be thrown but for some reason
// we just quit out of the method. Result would be the same if we put
// empty "return;" statement.
// If we explicitly put nil, like
// #"album_cover_photo_id": nil
// we get compiler error.
[albums addObject:album];
}
completionBlock(albums, nil);
}
}];
}
You wrote:
// Here an exception should be thrown but for some reason
// we just quit out of the method. Result would be the same if we put empty "return;" statement.
Most likely the exception is caught somewhere. Try putting a breakpoint on Objective-C exception throws (In Xcode, go to the Breakpoint tab, click + in the bottom left corner and select Add Exception Breakpoint. Make sure the parameters are set to Objective-C exception and break On Throw).
You claim that
NSString *albumID = dict[#"id"];
NSString *coverPhotoID = nil;
NSString *description = dict[#"description"];
NSString *name = dict[#"name"];
NSDictionary *album = #{#"album_id": albumID,
#"album_cover_photo_id": coverPhotoID,
#"album_description": description,
#"album_name": name };
doesn't crash your app.
Honestly, I don't believe it. Either you're trolling or you're misinterpreting whats happening.
As the first line of your for cycle log the dict dictionary:
NSLog(#"dict var: %#", dict);
Does dict contains the key album_cover_photo_id?
If you get something like
"album_cover_photo_id" : null
then NSString *coverPhotoID = dict[#"cover_photo"] is assigning an NSNull instance to coverPhotoID. In that case the app doesn't crash because an NSNull instance is not nil.
NSString *coverPhotoID = nil;
is different then
NSString *coverPhotoID = [NSNull null];
It's very common for servers to return nulls in JSON instead of omitting the key (server people are weird).
You can also log album after you create it
NSLog(#"album var: %#", album);
And if you are 100 % sure you're adding a nil to a dictionary, take it to the Apple developer forum. I bet they'd love to know about that bug.
I hope this helps you in some way.
Related
Crashlytics reported this crash in one of my apps and I am not able to reproduce it at all, no matter what I do.
This happens to about 5% of the users, so it's a pretty big deal.
I'm posting screenshots with the crash report and also the methods that are mentioned in the crash report.
Any idea how to solve this?
This is where the app crashed:
#pragma mark - custom transformations
-(BOOL)__customSetValue:(id<NSObject>)value forProperty:(JSONModelClassProperty*)property
{
if (!property.customSetters)
property.customSetters = [NSMutableDictionary new];
NSString *className = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:[value class]]);
if (!property.customSetters[className]) {
//check for a custom property setter method
NSString* ucfirstName = [property.name stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[property.name substringToIndex:1] uppercaseString]];
NSString* selectorName = [NSString stringWithFormat:#"set%#With%#:", ucfirstName, className];
SEL customPropertySetter = NSSelectorFromString(selectorName);
//check if there's a custom selector like this
if (![self respondsToSelector: customPropertySetter]) {
property.customSetters[className] = [NSNull null]; // this is line 855
return NO;
}
//cache the custom setter selector
property.customSetters[className] = selectorName;
}
if (property.customSetters[className] != [NSNull null]) {
//call the custom setter
//https://github.com/steipete
SEL selector = NSSelectorFromString(property.customSetters[className]);
((void (*) (id, SEL, id))objc_msgSend)(self, selector, value);
return YES;
}
return NO;
}
This is the originating method:
-(void)reloadUserInfoWithCompletion:(void (^) (LoginObject *response))handler andFailure:(void (^)(NSError *err))failureHandler {
NSString *lat;
NSString *lon;
lat = [NSString stringWithFormat:#"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.latitude];
lon = [NSString stringWithFormat:#"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.longitude];
NSMutableDictionary *params = [NSMutableDictionary new];
[params setObject:lat forKey:#"latitude"];
[params setObject:lon forKey:#"longitude"];
[[LoginHandler sharedInstance] getLoginToken:^(NSString *response) {
NSDictionary *headers;
if (response) {
headers = #{#"Login-Token":response};
}
GETRequest *req = [GETRequest new];
[req setCompletionHandler:^(NSString *response) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"response: %#",response);
NSError *err = nil;
self.loginObject.userDetails = [[User alloc] initWithString:response error:&err]; // <- this is the line reported in the crash
[self storeLoginObject];
NSLog(#"%#",self.loginObject.userDetails);
// [Utils updateFiltersFullAccessIfAll];
dispatch_async(dispatch_get_main_queue(), ^{
if (handler) {
handler(self.loginObject);
}
});
});
}];
[req setFailedHandler:^(NSError *err) {
if (failureHandler) {
failureHandler(err);
}
}];
NSLog(#"%#",params);
[req requestWithLinkString:USER_DETAILS parameters:nil andHeaders:headers];
}];
}
So setObject:forKey: can cause problems in two ways. 1. If object is nil or 2. the key is nil. Both could cause the crash you are seeing. Given that you are setting the object to [NSNull null] it is probably safe to assume that it is the key giving you problems (on line 855).
Walking back from there that would reveal that className is nil. If you look, your code does not protect against this. You make an assumption here that NSStringFromClass (a couple lines before) is giving you back a valid string, which assumes that the value originally passed into the method is non-nil. If it is nil it would make it past all of your checks, including !property.customSetters[className], since this would be !nil allowing it to enter the if.
If I am reading your code right (a bit hard since I cannot test any of my assumptions) NSLog(#"response: %#",response); would print out a nil response.
Try seeing how your code handles these unexpected nils and let me know in the comments how things go.
If you don't use model custom setters you can replace JSONModel __customSetValue:forProperty: with swizzling or Aspects library
#import "JSONModel+Aspects.h"
#import "JSONModel.h"
#import "Aspects.h"
#implementation JSONModel (Aspects)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[JSONModel aspect_hookSelector:#selector(__customSetValue:forProperty:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo) {
return NO;
} error:NULL];
});
}
#end
I am getting the error -[__NSCFString objectForKey:]: ever since I switched header request library to AFNetWorking. This is the code that is causing the error.
- (void) syncContentsFinish : (id) result
{
Content *content;
NSArray *contentsArray = [result objectForKey:#"content"];
for ( id object in contentsArray ) {
content = [Content getContentWithDictionary:object];
}
Specifically the content = method is causing this error. Here is the results I am getting from the server.
{
content = {
count = 0;
id = 42488267526162;
};
message = success;
responseCode = 200;
}
I have no idea what is causing this at all but any tips or suggestions are appreciated, also if you need me to post more code (such as the handler that is retrieving the data or the Content class please let me know.
Edit: Actually this is misleading. In the Content class this is what I have that is actually causing the App to crash.
+ (Content *) getContentWithDictionary : (NSDictionary *) dic
{
Content *content = [[Content alloc] init];
if ( [dic objectForKey:#"id"] != [NSNull null] ) {
content.contentId = [[dic objectForKey:#"id"] longLongValue];
}
Its look like Content is NSString not NSDictionary.
results is a dictionary, that much is correct. But the value of the "content" key is not an array, but another dictionary. Therefore, your for loop is actually iterating the keys of the dictionary. This means that each object is an NSString.
I have written the following code but I keep on getting nil. I have tried many different variations of this but I am failing exceptionally hard.
This is what I am getting from the server.
Two objects.
[{"description":"yolo.","name":"ye","id":1},{"description":"sMITH","name":"John","id":2}]
Any help would be greatly appreciated...... Thanks.
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&requestError];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSArray *jsonObjects = [jsonParser objectWithData:response];
NSMutableString *yolo = [[NSMutableString alloc] init];
for ( int i = 0; i < [jsonObjects count]; i++ ) {
NSDictionary *jsonDict = [jsonObjects objectAtIndex:i];
NSString *IDID = [jsonDict objectForKey:#"id"];
NSString *name = [jsonDict objectForKey:#"name"];
NSLog(#"ID: %#", IDID); // THIS DISPLAYS
[yolo appendString: IDID]; // THIS seems to be causing the new error...
[yolo appendString:#": "];
[yolo appendString: name];
NSLog(#"%#", yolo); // RETURNS NIL
}
EDIT:
currently my new error is...
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[NSDecimalNumber length]:
unrecognized selector sent to instance 0x81b89f0'
Looks like your [jsonDict objectForKey:#"id"] is an NSNumber(or NSDecimalNumber) and not an NSString. You should change the line NSString *IDID = [jsonDict objectForKey:#"id"]; to,
id myObject = [jsonDict objectForKey:#"id"];
NSString *IDID = nil;
if ([myObject isKindOfClass:[NSNumber class]]) {
IDID = [[jsonDict objectForKey:#"id"] stringValue];
} else {
IDID = [jsonDict objectForKey:#"id"];
}
This error appeared now since earlier you were not initializing NSMutableString *yolo and you were using appendString: on a nil object. Since now it is initialized as NSMutableString *yolo = [[NSMutableString alloc] init]; it is trying to call appendString on NSMutableString object which accepts only NSString type as its inputs where as you are passing an NSNumber in it. length is a method which appendString: internally calls. So you need to change this as well.
You never initialize yolo, so it's just nil the whole time you're calling -appendString: on it. Try this:
NSMutableString *yolo = [NSMutableString string];
Have you tried initializing the NSMutableString?
NSMutableString *yolo = [[NSMutableString alloc] init];
It looks like you are not really checking the type of the data coming to your app via your JSON feed. This might be the case of random crashes when users actually use your app. It might be also a reason for rejection to the App Store, is such crashes happen during your App's review.
You should be checking the type of all objects you receive from JSON, before calling methods on them :)
By implementing best practices you will have a stable and usable app. Build data models to validate your data. You can also you a JSON data model framework like JSONModel: http://www.jsonmodel.com/
It's obvious from your data that "id" is not a string, but a number. Assigning a pointer to an NSString* doesn't magically convert it to an NSString*. And it's obvious from the exception that you got that some object is an NSDecimalNumber when you thought it would be an NSString.
So: IDID is an NSNumber*, and pretending it is an NSString* will lead to crashes.
I am getting a SIGABRT crash sometimes with the crash saying: A GKScore must contain an initialized value.
So it tracked it down to this line:
[localScore reportScoreWithCompletionHandler:^(NSError* error) {}];
And localStore is created like this:
GKScore* localScore = [scores objectForKey:category];
-- category comes from...
for (NSString* category in categories)
-- categories comes from...
[GKLeaderboard loadCategoriesWithCompletionHandler:^(NSArray *categories, NSArray *titles, NSError *error)
-(void) initScores
{
NSString* libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* file = [libraryPath stringByAppendingPathComponent:currentPlayerID];
file = [file stringByAppendingString:kScoresFile];
id object = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if ([object isKindOfClass:[NSMutableDictionary class]])
{
NSMutableDictionary* loadedScores = (NSMutableDictionary*)object;
scores = [[NSMutableDictionary alloc] initWithDictionary:loadedScores];
}
else
{
scores = [[NSMutableDictionary alloc] init];
}
//NSLog(#"scores initialized: %d", scores.count);
}
Sorry for all the code but pretty much all this code comes from this library's file: https://github.com/csddavies/DDGameKitHelper/blob/master/DDGameKitHelper.m
Anyway how would I fix this?
Thanks!!!
From the GameKit reference:
To report a score to Game Center, your game allocates and initializes a new object, sets the value property to the score the player earned, and then calls the reportScoreWithCompletionHandler: method.
Most likely it's complaining because you haven't set the value property, but it's also possible that you're missing the first step too -- i.e. it doesn't like you submitting GKScore objects that came from a leaderboard instead of ones you create yourself.
This question already has an answer here:
EXC_BAD_ACCESS using iCloud on multiple devices
(1 answer)
Closed 9 years ago.
I have a pretty simple IOS app using iCloud document storage. Everything was working and then at some point I began encountering a EXC_BAD_ACCESS error in my document load method for at least one iCloud document, although most files load just fine.
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
file = (NSFileWrapper*) contents;
NSFileWrapper *infoFile = [[file fileWrappers] objectForKey:InfoFile];
NSData *infoData = [infoFile regularFileContents];
if(nil != infoData) {
NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0;
NSError *propertyListError;
// EXC_BAD_ACCESS occurs here
NSDictionary *dictionary = [NSPropertyListSerialization propertyListWithData:infoData options:NSPropertyListImmutable format:&format error:&propertyListError];
if(nil == propertyListError) {
_name = [dictionary objectForKey:#"name"];
_date = [dictionary objectForKey:#"date"];
_index = [dictionary objectForKey:#"index"];
_paperSize = [GritzPaperSizeEnum enumWithType:[dictionary objectForKey:#"paperSize"]];
TFLog(#"loading doc %#", _name);
_pages = [[NSMutableArray alloc] init];
for (NSString *key in file.fileWrappers) {
NSFileWrapper *subDir = [[file fileWrappers] objectForKey:key];
if(subDir.isDirectory) {
GritzPage *page = [[GritzPage alloc] initFromFile:subDir];
[_pages addObject:page];
}
}
_currentPage = [_pages objectAtIndex:0];
return YES;
}
}
return NO;
}
I would expect that I can 'catch' and handle bad data and ignore the corrupt file; but I can't seem to figure out how. A EXC_BAD_ACCESS error causes the app to crash.
What should I be doing differently to determine ahead of time that the data or file is going to fail and skip it (or delete it).
verify it is a NSFileWrapper using isKindOfClass, else treating it as one is weird (also look at the given typeName :))
using a #try { .. } #catch construct to catch any exception wont work in THIS case though as you cause a BAD_ACCESS which is a UNIX SIGNAL
The NSPropertyListFormat variable format should be declare as point. And i think you should call the propertyListWithData: Method with format as pointer not with the address of format.