为什么 NSInvocation return 值会创建一个僵尸?

Why NSInvocation return value creates a zombie?

我正在尝试构建 JavaScript 到 Native 的通信。为此,我需要在 JavaScript 调用时在某些 class 上动态执行一个方法。

我在 NSInvocation 获取 return 值时遇到问题。使用 getReturnValue 时,应用程序会因僵尸而崩溃。僵尸表示来自调用方法的 return 值的调用。

如果我注释掉 [invocation getReturnValue:&result]; 行,应用程序不会中断。

我目前调用的测试方法returns和(NSString *) 如果我将调用的选择器方法实现 return 设为类似于 @"firstsecond") 的文字字符串,应用程序也不会中断。

当调用方法已经执行并且字符串已 returned 时,为什么还需要以任何方式引用它。 returned 字符串不是复制到 id result.

- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {


    if ([@"Native_iOS_Handler" isEqualToString: message.name]) {
        NSArray *arguments = [message.body valueForKey:@"arguments"];
        NSNumber *callbackID = [message.body valueForKey:@"callbackID"];
        NSString *APIName = [message.body valueForKey:@"APIName"];
        NSString *methodName = [message.body valueForKey:@"methodName"];

        id classAPI = [self.exposedAPIs objectForKey:APIName];

        SEL methodToRun = [classAPI getSelectorForJSMethod:methodName];

        NSMethodSignature *methodSignature = [classAPI methodSignatureForSelector:methodToRun];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        [invocation setTarget:classAPI];
        [invocation setSelector:methodToRun];

        id result;
        [invocation invoke];
        [invocation getReturnValue:&result];
        NSLog(@"%@", result);// output: firstsecond
    }
}

//the selector in this case is this
-(NSString*)getFoo{
    // Why is this a zombie????
    return [NSString stringWithFormat:@"%@%@", @"first", @"second"];
    // This works:
    //return @"fristsecond"
}

尽管 Instruments 中的选择器不同,但结果是相同的。从这张图片我明白了我告诉你的。我没有使用仪器的经验。

您成为 ARC 的受害者,您没有意识到 NSInvocation 通过另一个指针间接修改 result 的工作方式。这是一个已知问题 here

结果对象间接地等于result,但ARC 不知道它并且永远不会保留它。

无需过多赘述 NSString 是一个 class 集群 。它实际上意味着下面的实现会根据字符串的创建和使用方式而变化。在 obj-c 中与它交互时,它的详细信息是隐藏的,Apple 付出了很多努力使其与 iOS 开发人员无缝衔接。你的情况有些特殊。

通常你会得到:

  1. __NSCFConstantString(例如 @"constant")- 应用程序生命周期的字符串常量,对于您的情况,它 碰巧 可以工作,但您应该 从不依赖那个
  2. NSTaggedPointerString(例如 [[@"a"] mutableCopy] copy])- 具有内部查找的优化短字符串 table.
  3. __NSCFString(如[@"longString" mutableCopy] copy])长字符串CoreFoundation表示。

随时 NSString 可能会更改底层的实现,因此您永远不应对其做出假设。案例 3 返回后会立即超出范围并在下一个 运行 循环中被释放,案例 1 永远不会被释放,案例 2 (?),但肯定会在下一个 运行 循环中存活下来。

所以基本上 ARC 不够聪明,无法将可能释放的对象与 id result 相关联,而您 运行 陷入您的问题。

如何解决?

使用其中之一:

1.

void *tempResult;
[invocation getReturnValue:&tempResult];
id result = (__bridge id) tempResult;

2.

__unsafe_unretained id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;

编辑

正如@newacct 在他的评论中指出的那样,getReturnValue: 没有注册 weak 指针,因此在这种情况下 不合适 。当对象被释放时,指针 不会清零

3.

__weak id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;