为什么 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 开发人员无缝衔接。你的情况有些特殊。
通常你会得到:
__NSCFConstantString
(例如 @"constant"
)- 应用程序生命周期的字符串常量,对于您的情况,它 碰巧 可以工作,但您应该 从不依赖那个
NSTaggedPointerString
(例如 [[@"a"] mutableCopy] copy]
)- 具有内部查找的优化短字符串 table.
__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;
我正在尝试构建 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 开发人员无缝衔接。你的情况有些特殊。
通常你会得到:
__NSCFConstantString
(例如@"constant"
)- 应用程序生命周期的字符串常量,对于您的情况,它 碰巧 可以工作,但您应该 从不依赖那个NSTaggedPointerString
(例如[[@"a"] mutableCopy] copy]
)- 具有内部查找的优化短字符串 table.__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;