3

Here is a situation: Hockeyapp and testflight every now and then complain about me

"attempting to insert nil object"

in mutable dictionaries/arrays. I know the right thing is to check for nil all the time, and I do when it makes sense.. Our testers can not catch those crashes, but AppStore users obviously can.

My guess is that sometimes server returns NSNulls when it should not. So not to insert checks for nil everywhere in the huge project my idea was to create a separate target for the testers and use method swizzling for collection classes. Say, I'll replace insertObject:atIndex with my swizzled_insertObject:atIndex, where if the object is actually nil I log/show a descriptive report before it crashes.

The thing is I can not use swizzling for __NSPlaceholderDictionary or __NSArrayM (just because I can not make a category on private classes) and that makes me sad.

So basically I'm asking for advice on how to catch those nasty rare crashes. One solution I have in mind is using try-catch blocks, I know they are expensive in Objective-c, so I'd not use them in production, just for testers. But methods surrounded by try-catche-s surrounded by #ifdef-#endif-s will erase all the readableness of the code. So I'm searching for a more elegant solution. Thanks.

Update: the stack traces are unfortunaely not very descriptive, here is what I get

Exception Type:  SIGABRT
Exception Codes: #0 at 0x3a378350
Crashed Thread:  0

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[2]'

Last Exception Backtrace:
0   CoreFoundation                      0x321522a3 <redacted> + 163
1   libobjc.A.dylib                     0x39e7a97f _objc_exception_throw + 31
2   CoreFoundation                      0x320a355f <redacted> + 135
3   CoreFoundation                      0x320da0d3 <redacted> + 51
....
dariaa
  • 6,285
  • 4
  • 42
  • 58
  • 1
    You don't need to check for `nil` everywhere. The stack traces should be telling you exactly which line of code in your app is causing the problem. Fix that specific code. But don't just check for `nil`. Determine why you are getting `nil` in the first place. – rmaddy Jun 27 '13 at 14:37
  • 1
    That's the problem - I can not determine why and where I'm getting nil. and the stack traces are not always descriptive all I get is for instance: erminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[2]' – dariaa Jun 27 '13 at 14:39
  • One other thing: You seem to be confusing `nil` with `NSNull`. `NSNull` is an object; it's commonly used as a placeholder. – Extra Savoir-Faire Jun 27 '13 at 14:43
  • I know the "why" can be hard to determine sometimes but there "where" should be clear from the stack traces you are getting. Are you getting properly symbolicated crash reports? – rmaddy Jun 27 '13 at 14:43
  • trudyscousin, is what hockayapp gives me for these crashes from the "live" app in appstore. I'm sorry it's "silly nonsense". That's why I'm asking for advice how to determine what's wrong if analyzing backtrace does not help. Again, sorry if it's silly – dariaa Jun 27 '13 at 14:59
  • No worries. And don't give up on hockeyapp because of this. It actually worked much better than testflight for me. It gives nice stack traces for adhoc builds. I don't think I can get a stack trace for those crashes, they even do not appear in itunes connect for some reason. – dariaa Jun 27 '13 at 15:22
  • 1
    This is Andreas from HockeyApp. `` is present in stack traces because iOS puts them in there. For all public (non NDA) iOS releases, HockeyApp does replace this with the correct system calls. The reason why `` appears in the first place is because of iOS internal optimizations. Anyway, if you need help, you should post the complete stack trace instead of just the first 4 lines! What iTunes Connect shows and what it doesn't is explained here: http://stackoverflow.com/questions/15588072/users-say-that-the-ios-app-crashes-without-any-further-explanation/15592723#15592723 – Kerni Jul 02 '13 at 15:26
  • 1
    @trudyscousin Just to make sure, the stack trace HockeyApp reports is near identical what iOS writes in its crash reports. There is nothing wrong with the reports per se. And as explained in my previous comment here, HockeyApp is doing nothing silly either. – Kerni Jul 02 '13 at 15:28

6 Answers6

7

You don't need to add a category to do method swizzling. I was able to isolate a crash like this by method swizzling initWithObjects:forKeys:count: and putting a try/catch around the original method call. Finally I added a breakpoint in the catch section. This allowed me to break and go back up the stack to where the nil value was being used. This code is added at the top of my AppDelegate.m:

#import <objc/runtime.h>
#import <objc/message.h>
static id safe_initWithObjects(id self, SEL _cmd, const id objects[], const id <NSCopying> keys[], NSUInteger count) {
    id orignialResult = nil;
    @try {
        orignialResult = objc_msgSend(self, @selector(safe_initWithObjects:forKeys:count:), objects, keys, count);
    }
    @catch (NSException *exception) {
        NSLog(@"BUSTED!"); // put breakpoint here
    }

    return orignialResult;
}

And then in my app did finish launching method:

Class target = NSClassFromString(@"__NSPlaceholderDictionary");
class_addMethod(target, @selector(safe_initWithObjects:forKeys:count:), (IMP)&safe_initWithObjects, "@@:**L");

Method m1 = class_getInstanceMethod(target, @selector(safe_initWithObjects:forKeys:count:));
Method m2 = class_getInstanceMethod(target, @selector(initWithObjects:forKeys:count:));
method_exchangeImplementations(m1, m2);
Dav Yaginuma
  • 691
  • 7
  • 14
  • 1
    This should be an accepted answer! The swizzling helped me to find the crash in such a place I would never think about. – Yevhen Dubinin Sep 25 '14 at 14:16
  • 1
    This has helped me a number of times. You can save yourself the trouble/errors of trying to work out the method type encoding ( "@@:**L" ) by letting the Obj-C runtime work it out for you: Method m1 = class_getInstanceMethod(target, @selector(initWithObjects:forKeys:count:)); const char *methodTypeEncoding = method_getTypeEncoding(m1); Then you can then pass this into class_addMethod instead of a hard-coded string. – user2067021 Oct 13 '14 at 23:56
3

One thing to note on the crash message itself.

"attempt to insert nil object from objects[#]" means that either the key or value at index [#] (thinking of a NSDictionary literal list) is nil.

Say I have a dictionary literal

NSDictionary *person = @{@"first":firstName,@"last":lastName,@"email":email"};

Then index[0] would be the first pair in the list index[1] would be the second pair and so on. If either entry in the pair is nil it will trigger this exception with the corresponding index.

SuperGuyAbe
  • 525
  • 1
  • 5
  • 5
1

I tried to answer the third answer, it does give me help in most cases. However, when my project support framework 64, I met a disastrous Ben collapse, caused by a third party static library. Ben collapse points CFDictionaryContainsKey.

CFDictionaryRef myDictionaryRef = ......
CFDictionaryContainsKey (myDictionaryRef, searchKey [0]). 

When Ben collapse myDictionaryRef is nil. After some testing, I found a problem.

interface __NSPlaceholderDictionary: NSMutableDictionary {
}
- (Id) initWithObjects: (const id *) arg1 forKeys: (const id *) arg2 count: (unsigned int) arg3;

Compare answers

static id safe_initWithObjects (id self, SEL _cmd, const id objects [],   const id <NSCopying> keys [], NSUInteger count) {.....}

Different types of parameters caused Ben collapse. so, I think the correct answer is

  #import <objc / runtime.h>
  #import <objc / message.h>
  static id safe_initWithObjects (id self, SEL _cmd, const id * objects, const id * keys, unsigned int count) {
      id orignialResult = nil;
     @try {
           orignialResult = objc_msgSend (self,selector (safe_initWithObjects: forKeys: count :), objects, keys, count);
          }
     @catch (NSException *exception) {
           NSLog(@"BUSTED!"); // put breakpoint here
         }

return orignialResult;
 }

  Class target = NSClassFromString (@ "__ NSPlaceholderDictionary");
  class_addMethod (target,selector (safe_initWithObjects: forKeys: count :), (IMP) & safe_initWithObjects, "@@: ** L");

 Method m1 = class_getInstanceMethod (target,selector (safe_initWithObjects: forKeys: count :));
 Method m2 = class_getInstanceMethod (target,selector (initWithObjects: forKeys: count :));
 method_exchangeImplementations (m1, m2);

Thanks answers providers, but also because I have enough reputation 50. I can not comment directly. I hope you give me a vote.

0

assume that you fetch data using JSON from server, some times a field in the JSON data is null,so you may get NSNull after coversion.

so my advice is checking "null" situation in server, if it occurs, return the faild msg ,but not deliver the bad format data to APP.

when APP receive such those data, APP won't know how to handle it. it's already abnormal. it's okay that you can catch the exception and ignore the abnormal data, but my way to do is to prevent it happens from the source.

adali
  • 5,977
  • 2
  • 33
  • 40
0

In most system calls ( iOS) or server communication can be nil, or when you are working , calling function from third party libs. On those cases it is a must to check a nil value imho. Several senior developers, architects use a multiple statement in on line. Very bad behavior, I lernt my lesson, just write in separate line to know which line crashed exactly the code. A lot easier to fix it.

If you can't find in your code. There are loggers, crash reporter libraries. Use any of than and you will get the causes, line number, easy to fix than.

I wouldn't use everywhere nil checking nor try-catch, just in cases mentioned above

  • Thank you for your answer, I do use hockeyapp and upload dsyms but the stack traces are still not very descriptive. Please, see the updated question – dariaa Jun 27 '13 at 14:49
0

Just found this by looking at where I'm creating NSDictionaries and adding a few assertion statements to check if objects are nil.

  1. search for @{@" - start of creating a dictionary using modern syntax, or NSDictionary
  2. Add NSAssert(object != nil, @"Your Object is is nil here"); before inserting objects
  3. Run the app, check console, find the corresponding assert and fix it:

    Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Your Object is is nil here'

Alex Stone
  • 46,408
  • 55
  • 231
  • 407