关于如何从所需设备捕获“尝试插入nil对象”的建议

时间:2013-06-27 14:34:45

标签: ios objective-c runtime objective-c-category swizzling

这是一种情况: Hockeyapp和testflight时不时地抱怨我

  

“尝试插入nil对象”

在可变字典/数组中。我知道正确的方法是一直检查nil,并且当它有意义时我会这样做。我们的测试人员无法捕获那些崩溃,但AppStore用户显然可以。

我的猜测是,有时服务器会返回NSNulls。 因此,不要在巨大的项目中插入nil的检查,我的想法是为测试人员创建一个单独的目标,并使用方法调整集合类。 说,我将insertObject:atIndex替换为我的swizzled_insertObject:atIndex,如果对象实际上是零,我会在崩溃之前记录/显示描述性报告。

问题是我无法对__NSPlaceholderDictionary__NSArrayM进行调整(仅仅因为我无法在私人课程上制作类别)这让我感到难过。

所以基本上我正在征求关于如何捕捉那些令人讨厌的罕见崩溃的建议。 我想到的一个解决方案是使用try-catch块,我知道它们在Objective-c中很昂贵,所以我不会在生产中使用它们,仅供测试人员使用。但try-catche包围的方法被#ifdef - #endif - s包围,将清除代码的所有可读性。所以我正在寻找更优雅的解决方案。 谢谢。

更新:堆栈跟踪不一定不是很具描述性,这是我得到的

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 个答案:

答案 0 :(得分:7)

您不需要添加类别来进行方法调整。我能够通过方法调整initWithObjects:forKeys:count:并在原始方法调用周围放置try / catch来隔离这样的崩溃。最后我在catch部分添加了一个断点。这允许我打破并返回堆栈到使用nil值的位置。此代码添加在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;
}

然后在我的应用程序中完成了启动方法:

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

答案 1 :(得分:3)

关于崩溃消息本身需要注意的一件事。

尝试从对象中插入nil对象[#] ”表示索引[#]中的键或值(考虑到NSDictionary文字列表)为零。

说我有字典文字

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

然后索引[0]将成为列表中的第一对索引[1]将成为第二对,依此类推。如果该对中的任何一个条目为nil,它将使用相应的索引触发此异常。

答案 2 :(得分:0)

假设您从服务器使用JSON获取数据,有时JSON数据中的字段为空,因此您可能在转换后获得NSNull。

所以我的建议是检查服务器中的“null”情况,如果发生,返回faild消息,但不会将错误的格式数据传递给APP。

当APP收到这些数据时,APP将不知道如何处理它。它已经异常了。 没关系,你可以捕获异常并忽略异常数据,但我的方法是防止它从源发生。

答案 3 :(得分:0)

在大多数系统调用(iOS)或服务器通信可以是零,或者当您工作时,从第三方库调用函数。 在这些情况下,必须检查零值imho。 一些高级开发人员,架构师在线使用多个语句。 非常糟糕的行为,我借助我的教训,只是写在单独的行,知​​道哪条线正好崩溃了代码。更容易修复它。

如果在代码中找不到。有记录器,崩溃记者库。使用任何一个,你会得到原因,行号,易于修复。

我不会在任何地方使用nil检查或try-catch,只是在上面提到的情况下

答案 4 :(得分:0)

通过查看我正在创建NSDictionaries的位置并添加一些断言语句以检查对象是否为零来找到这个。

  1. 搜索@{@" - 开始使用现代语法创建字典,或NSDictionary
  2. 添加NSAssert(对象!= nil,@“你的对象在这里是零”);在插入对象之前
  3. 运行应用程序,检查控制台,找到相应的断言并修复它:

    由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'你的对象在这里是零'

答案 5 :(得分:0)

我试着回答第三个答案,它在大多数情况下确实给了我帮助。但是,当我的项目支持框架64时,我遇到了由第三方静态库引起的灾难性Ben崩溃。 Ben崩溃点CFDictionaryContainsKey。

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

当Ben崩溃时,myDictionaryRef为零。 经过一些测试,我发现了一个问题。

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

比较答案

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

不同类型的参数导致Ben崩溃。 所以,我认为正确答案是

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

感谢答案提供者,也因为我有足够的声誉50.我不能直接评论。我希望你能投票给我。