3

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

“试图插入零对象”

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

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

问题是我不能使用 swizzling for __NSPlaceholderDictionaryor __NSArrayM (只是因为我不能在私人课程上创建一个类别),这让我很伤心。

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

更新:不幸的是,堆栈跟踪不是很有描述性,这就是我得到的

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
....
4

6 回答 6

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);
于 2014-05-21T16:52:33.350 回答
3

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

尝试从 objects[#] 插入 nil 对象”意味着索引 [#] 处的键或值(考虑 NSDictionary 文字列表)为 nil。

说我有字典文字

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

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

于 2015-02-11T13:38:03.693 回答
1

我试图回答第三个答案,在大多数情况下它确实给了我帮助。然而,当我的项目支持框架 64 时,我遇到了一个灾难性的 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。我不能直接评论。我希望你给我一票。

于 2015-05-15T03:58:57.610 回答
0

在大多数系统调用 (iOS) 或服务器通信中,可以为零,或者在您工作时,从第三方库调用函数。在这些情况下,必须检查 nil 值恕我直言。几位高级开发人员、架构师在网上使用多重声明。非常糟糕的行为,我吸取了教训,只需在单独的行中编写即可知道哪一行完全崩溃了代码。更容易修复它。

如果您在代码中找不到。有记录器、崩溃报告库。使用任何比,你会得到原因,行号,比容易修复。

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

于 2013-06-27T14:43:56.573 回答
0

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

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

当APP接收到这些数据时,APP将不知道如何处理它。已经不正常了。可以捕获异常,忽略异常数据是可以的,但我的做法是从源头上防止它发生。

于 2013-06-27T14:42:57.020 回答
0

刚刚通过查看我在哪里创建 NSDictionaries 并添加一些断言语句来检查对象是否为 nil 发现了这一点。

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

    由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“您的对象在这里为零”

于 2014-06-09T22:57:03.863 回答