Advice on how to catch “attempt to insert nil object” from a device needed

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

“attempting to insert nil object”

  • How to swizzle a class method on iOS?
  • My isa-swizzling breaks KVO
  • App Store - Method Swizzling Legality
  • Swizzling a method with variable arguments and forward the message - Bad Access
  • Objective C Method Swizzling using dynamic library
  • Swift function swizzling / runtime
  • 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
    ....
    

    6 Solutions Collect From Internet About “Advice on how to catch “attempt to insert nil object” from a device needed”

    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);
    

    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.

    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.

    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

    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’

    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.