使用ARC进行不一致的对象释放?

时间:2012-02-07 08:32:16

标签: objective-c automatic-ref-counting

我正在使用一个简单的命令行应用程序来处理内存(de)分配内容,该应用程序适用于使用启用了ARC的Xcode版本4.2.1构建的Mac OSX 10.7以及默认的构建设置。我无法解释我从我对ARC的理解中得到的行为,所以我希望有人可以解释这里发生了什么。

首先,在下面的代码中我得到了我期望的行为(请注意NLog()输出在相应语句后的注释中给出)

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1;
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>
    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>
    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // (null)
    return 0;
}

所以在上面,在分配了weakRef之后,NSObject实例有两个强指针,因此保留计数为2.在将objPtr1归零后,仍有一个指向实例的保留指针,所以它仍然在内存中响应描述消息。在nir-ing objPtr2之后,没有指向该对象的强指针并且它被解除分配(我假设它是,因为weakRef已被置零)。到目前为止,非常好。

现在,改变相同的代码:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __unsafe_unretained NSObject *weakRef = objPtr1; // __unsafe_unretained instead of just __weak
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>
    //why was the object instance not deallocated and the preceding statement not crash the program?
    return 0;
}

我希望weakRef成为一个悬空指针,发送一条消息,通过该消息会导致程序在第三个NSLog()语句中崩溃,但似乎对象实例仍然存活且很好。

另一件我觉得奇怪的事情是:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{

    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1; // __weak again
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    return 0;

}

这最后一个代码就像第一个代码(使用归零的__weak指针);唯一的区别是描述消息是在三个NSLog()调用的每一个中通过weakRef发送给对象的。但是这一次,即使在删除了两个强引用之后,对象也没有被释放(因为它仍然通过weakRef响应消息)。

那么这里发生了什么?

2 个答案:

答案 0 :(得分:3)

如果您反汇编A.R.C.生成的代码,每次访问弱变量都会包含在对此函数的调用中:

id objc_loadWeak(id *location)
{
    return objc_autorelease(objc_loadWeakRetained(location));
}

检查对象是否已被解除分配,如果没有,则保留并自动释放它,以防止过早的deallocs。

因此,在你的第三个例子中,对weakRef方法的早期调用会导致其保留计数增加,因此使指针无效会导致它被解除分配。

答案 1 :(得分:1)

这看起来很奇怪。你(在你的评论中)关于代码的第二位是正确的,因为内存还没有被重用。但第3位代码更奇怪。这是一个更简化的测试用例,它显示了这个奇怪的问题:

#import <Foundation/Foundation.h>

@interface SomeClass : NSObject 
@end

@implementation SomeClass
- (void)foo {
}
@end

int main (int argc, const char * argv[]) {
    @autoreleasepool {
        SomeClass *objPtr1 = [[SomeClass alloc] init];
        __weak SomeClass *weakRef = objPtr1;

//        [weakRef foo];
        [weakRef foo];

        objPtr1 = nil;

        NSLog(@"%p", weakRef);

        return 0;
    }
}

在该行注释掉输出后:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:39:42.769 test[6684:707] 0x0

如果该行未注释,则输出为:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:42:04.346 test[6688:707] 0x100f13f50

这看起来很奇怪,对我来说看起来完全像个错误。我实际上并不知道答案是什么,但我认为我会发布这个答案,以便弄清楚究竟发生了什么。

<强>更新

如果您在O0构建此代码,则只有在{em>没有调用weakRef时,foo似乎才会归零。单次调用foo将意味着它不会被归零。