我在Objective-C世界中刷新了我的知识,现在我正在用__weak
局部变量测试一些ARC。
我有非常简单的代码与这样的文件GAObject.h
#import <Foundation/Foundation.h>
@interface GAObject : NSObject
+ (instancetype)create;
@end
执行此接口GAObject.h
#import "GAObject.h"
@implementation GAObject
+ (instancetype)create {
return [[GAObject alloc] init];
}
- (void)dealloc {
NSLog(@"GAObject is being deallocated");
}
@end
所以有简单的工厂方法create
和我重写dealloc
方法来观察对象是否在我期待这个时被解除分配。现在有趣的部分main.m
:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Learning/GAObject.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"1");
NSObject *o1 = [[GAObject alloc] init];
NSObject * __weak weakObject = o1; // Line 1
o1 = nil; // o1 should be deallocated because there is no strong references pointing to o1.
NSLog(@"2");
NSObject *o2 = [GAObject create]; // Line 2
o2 = nil; // o2 should be deallocated here too but it is not deallocated. Why?
NSLog(@"3");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在输出中我看到:
1
GAObject is being deallocated
2
3
但我期待的结果应该是:
1
GAObject is being deallocated
2
GAObject is being deallocated
3
如果我使用工厂方法创建o2
然后我有这种行为。如果我像这样创建o2
:[[GAObject alloc] init]
然后我得到预期的输出。另外我注意到当我用weakObject
删除线时,我也得到预期的结果。有人可以解释一下吗?
这是因为ARC仍然尊重Cocoa内存管理命名约定。
根据这些约定,名为+create
的方法返回+0引用。因此,在该方法的实现中,ARC必须通过自动释放参考来平衡alloc
/ init
对的+1引用。
然后,在main()
中,ARC必须假设它已经从+create
的调用中获得了+0参考。如果它需要引用以在当前范围内存活,它将保留它,但它不会如此。当自动释放池耗尽时,第二个GAObject
实例将被释放,但这将永远不会发生,因为UIApplicationMain()
永远不会返回。如果您使用两个单独的自动释放池,一个用于处理GAObject
s的代码,另一个用于调用UIApplicationMain()
,我希望您能得到您期望的结果。
如果ARC确实需要引用存活,它将保留在强变量的赋值中,并在该变量被赋值为新值(包括nil
)或超出范围时释放。 ARC具有运行时优化,其中被调用者中的自动释放返回和调用者中返回值的保留相互抵消,使得对象永远不会放入自动释放池中。如果发生这种情况,您将获得预期的结果。
事实上,我的期望是编译器最初会发出保留和释放,即使在你的情况下,但后续的传递删除了多余的保留和释放。您的示例在保留之后立即发布,这使得编译器更加明显该对是冗余的。因为保留被删除,所以自动释放优化不会启动,并且对对象的引用确实会被放入自动释放池中。
如果你的方法被命名为+newGAObject
,那么命名约定意味着它返回一个+1引用,这一切都会改变。 (当然,就目前情况而言,你的+create
方法与内置的+new
方法完全相同,除了ARC必须添加的自动释放。所以,你可以改变调用代码来使用+new
,那就是也回避了这个问题。)
我不知道为什么与weakObject
的关系很重要。但是,由于您所看到的行为取决于某些优化,因此任何可以调整优化的内容都可以改变结果。