NSInvocation getReturnValue: 在 forwardInvocation: 方法中调用会使得返回的对象调用 dealloc: 方法

7

这是一个独立的test.m文件,我正在用它来测试行为。

编译方法: clang test.m -o test.app -fobjc-arc -ObjC -framework Foundation。请确保已安装Xcode命令行工具。

#import <Foundation/Foundation.h>

@protocol Protocol

@optional
- (id)objProxyMethod;

@end

@interface ReturnObject: NSObject

@end

@interface Test : NSObject <Protocol>

@end

@interface Proxy : NSObject <Protocol>

- (id)objProxyMethod;

@end

@implementation ReturnObject

- (void)dealloc {
    NSLog(@"ERROR:");
    NSLog(@"I'm getting deallocated!");
    NSLog(@"This shouldn't happen!");
}

- (NSString *)description {
    return @"Blank object!";
}

@end

@implementation Proxy

- (id)objProxyMethod {
    NSLog(@"in [Proxy objProxyMethod]!");
    return [[ReturnObject alloc] init];
}

@end

@implementation Test

- (void)forwardInvocation:(NSInvocation *)invocation {
    NSLog(@"Forwarded invocation!");
    Proxy *proxy = [[Proxy alloc] init];
    [invocation invokeWithTarget: proxy];
    NSUInteger length = [[invocation methodSignature] methodReturnLength];
    if (length == 8) {
        id result;
        [invocation getReturnValue:&result];
    }
}

@end

int main () {
    Test *test = [[Test alloc] init];
    id objResult = [test objProxyMethod];
    NSLog(@"objResult = \"%@\"", objResult);

    return 0;
}

如果我将[invocation getReturnValue:&result];注释掉,返回对象就不会被dealloc。我不知道这是一个错误,还是我误解了NSInvocation的工作原理。
3个回答

25
问题在于result默认是__strong类型的,所以当它超出作用域时,编译器会生成一个release。但是getReturnValue:没有将返回的对象所有权传递给你的方法,因此你的方法不应该释放它。
你可以通过改变result的声明来解决这个问题:
__unsafe_unretained id result;

result超出其范围时,这会防止编译器为result生成一个release。如果您需要保留它,则可以将其复制到另一个__strong变量中。

您还可以添加一个分类到NSInvocation来自动处理此问题:

@interface NSInvocation (ObjectReturnValue)

- (id)objectReturnValue;

@end

@implementation NSInvocation (ObjectReturnValue)

- (id)objectReturnValue {
    __unsafe_unretained id result;
    [self getReturnValue:&result];
    return result;
}

@end

...
    if (length == 8) {
        id result = [invocation objectReturnValue];
    }
...

你也可以将此报告为一个 bug。我会期望编译器或至少静态分析器能够警告你正在把指向强类型 id 的指针转换为 void 指针。http://bugreport.apple.com


谢谢!如果我使用__weak而不是__unsafe_unretained,它还能正常工作吗?到目前为止,当我使用__weak时没有出现任何错误,但我只是想确认一下。 - ryanrhee
在这里不应该使用__weak而应该使用__unsafe_unretained。弱引用变量需要使用objc_storeWeak运行时函数进行设置,而-[NSInvocation getReturnValue:]显然不会这样做。 - rob mayoff
如果你声明__weak id result,编译器会在result超出作用域时生成对objc_destroyWeak的调用。由于result没有使用objc_storeWeak进行设置,因此行为是未定义的。 - rob mayoff

4
因为 ARC 无法处理指针写作的对象,只能进行直接赋值。 错误:
id result;
[invocation getReturnValue:&result];

正确:

void *pointer;
[invocation getReturnValue:&pointer];

id result = (__bridge id)pointer; //Correct, ARC will retain pointer after assignment

0
if (length == 8) {
    id result; //this is nil (its also a stack allocated pointer)
    [invocation getReturnValue:&result];  //sets the value to an object
}

...method ends object is deallocated

你必须将结果设置为非堆栈分配的指针,或者不调用getReturnValue。

API 可能会假设由于您调用了getReturnValue,因此您将保留(并可能使用)返回值。但是您没有这样做。当您删除getReturnValue时,返回值是否在主方法中正确返回? 苹果文档说返回值会自动返回。

我认为它会。


我正在使用ARC,变量默认为__strong,因此retain操作正在进行,只是不可见。 - ryanrhee
id 是一个栈分配的变量。它不在堆上,它在方法调用结束时就不存在了。ARC 可能会在那里插入一个释放操作,因为该值没有在调用栈上显式返回。 - deleted_user
1
这就是为什么我不使用ARC。很高兴你得到了答案。 - deleted_user
是的,ARC 似乎在作用域结束时插入了一个 release - ryanrhee
如果将值设置为不在堆栈上的某些内容,ARC 可能会让其存活,但关于使用 __magic 的答案是值得知道的。 - deleted_user
我认为你是正确的。如果我使用malloc()和长度来分配堆上的空间,然后将该位置作为指针使用,那么我就不会有问题了。然后我只需free()该指针,所指向的对象就不会被释放。但我也同意,了解__magic是很好的。 - ryanrhee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接