在解除分配实例期间,预计不会出现 ARC 的奇怪行为
Not expected strange behaviour of ARC during deallocating instances
我正在更新我在 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
return 的方法是 +0 引用。因此,在该方法的实现中,ARC 必须通过自动释放引用来平衡 alloc
/init
对的 +1 引用。
然后,在 main()
中,ARC 必须假设它已从对 +create
的调用中收到 +0 引用。如果它需要引用在当前范围内存活,它会保留它,但它不会保留它。第二个 GAObject
实例将在自动释放池被耗尽时被释放,但这永远不会发生,因为 UIApplicationMain()
永远不会 returns。如果您使用两个单独的自动释放池,一个用于处理 GAObject
的代码,另一个用于调用 UIApplicationMain()
,我希望您会得到您期望的结果。
如果 ARC 确实需要引用来生存,它会保留对强变量的赋值,并在该变量被分配新值(包括 nil
)或超出范围时释放。 ARC 有一个 运行-time 优化,被调用者中的自动释放-return 和调用者中 returned 值的保留相互抵消,这样对象就永远不会被放置在自动释放池中。如果发生这种情况,您将获得预期的结果。
事实上,我的期望是即使在您的情况下,编译器最初也会发出保留和释放,但随后的传递会删除多余的保留和释放。您的示例在保留之后立即发布,这使得编译器更加明显地认为这对是多余的。因为保留被删除,自动释放优化不会启动,并且对您的对象的引用确实会被放入自动释放池中。
如果您的方法被命名为 +newGAObject
,那么命名约定将意味着它 return 是一个 +1 引用,这一切都会改变。 (当然,就目前而言,你的 +create
方法只是做与内置 +new
方法相同的事情,除了 ARC 必须添加的自动释放。所以,你可以改变调用代码以使用 +new
,这也将回避这个问题。)
我不知道为什么带有 weakObject
的那一行很重要。但是,由于您看到的行为取决于某些优化,任何可以调整优化的东西都可以改变结果。
我正在更新我在 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
return 的方法是 +0 引用。因此,在该方法的实现中,ARC 必须通过自动释放引用来平衡 alloc
/init
对的 +1 引用。
然后,在 main()
中,ARC 必须假设它已从对 +create
的调用中收到 +0 引用。如果它需要引用在当前范围内存活,它会保留它,但它不会保留它。第二个 GAObject
实例将在自动释放池被耗尽时被释放,但这永远不会发生,因为 UIApplicationMain()
永远不会 returns。如果您使用两个单独的自动释放池,一个用于处理 GAObject
的代码,另一个用于调用 UIApplicationMain()
,我希望您会得到您期望的结果。
如果 ARC 确实需要引用来生存,它会保留对强变量的赋值,并在该变量被分配新值(包括 nil
)或超出范围时释放。 ARC 有一个 运行-time 优化,被调用者中的自动释放-return 和调用者中 returned 值的保留相互抵消,这样对象就永远不会被放置在自动释放池中。如果发生这种情况,您将获得预期的结果。
事实上,我的期望是即使在您的情况下,编译器最初也会发出保留和释放,但随后的传递会删除多余的保留和释放。您的示例在保留之后立即发布,这使得编译器更加明显地认为这对是多余的。因为保留被删除,自动释放优化不会启动,并且对您的对象的引用确实会被放入自动释放池中。
如果您的方法被命名为 +newGAObject
,那么命名约定将意味着它 return 是一个 +1 引用,这一切都会改变。 (当然,就目前而言,你的 +create
方法只是做与内置 +new
方法相同的事情,除了 ARC 必须添加的自动释放。所以,你可以改变调用代码以使用 +new
,这也将回避这个问题。)
我不知道为什么带有 weakObject
的那一行很重要。但是,由于您看到的行为取决于某些优化,任何可以调整优化的东西都可以改变结果。