我正在刷新我在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
行时,我也得到了预期的结果。有人可以解释吗?
答案 0 :(得分:2)
这是因为ARC仍然遵守Cocoa内存管理命名约定。
在这些约定下,名为+create
的方法将返回+0引用。因此,在该方法的实现中,ARC必须通过自动释放参考来平衡alloc
/ init
对的+1参考。
然后,在main()
中,ARC必须假定它从对+create
的调用中收到+0引用。如果它需要引用才能在当前范围内存活,它将保留它,但事实并非如此。当自动释放池耗尽时,第二个GAObject
实例将被释放,但这将永远不会发生,因为UIApplicationMain()
永远不会返回。如果您使用两个单独的自动释放池,一个用于处理GAObject
的代码,另一个用于调用UIApplicationMain()
的代码,那么我希望您会得到预期的结果。
如果ARC确实需要引用来生存,它将保留在强变量的分配中,并在为该变量分配了新值(包括nil
)或超出范围时释放。 ARC具有运行时优化功能,即被调用方中的自动释放返回和调用者中保留的返回值相互抵消,从而使该对象永远不会放入自动释放池中。如果发生这种情况,您将获得预期的结果。
实际上,我的期望是,即使您遇到这种情况,编译器最初也会发出该保留和释放,但是随后的遍历将删除多余的保留和释放。您的示例在保留之后立即发布了该版本,这使编译器更清楚地知道该对是多余的。因为保留被删除,所以自动发布优化不会启动,并且对您对象的引用确实会放入自动发布池中。
如果您的方法名为+newGAObject
,则命名约定将意味着它返回+1引用,并且所有这些都将更改。 (当然,就目前情况而言,您的+create
方法与内置+new
方法的作用相同,除了必须添加ARC的自动发布。因此,您可以更改调用代码以使用+new
,这也将回避此问题。)
我不知道为什么weakObject
行很重要。但是,由于您看到的行为取决于某些优化,因此任何可以优化的东西都可以改变结果。