疑惑一:临时变量释放时机
1.1 大神的测试
首先站在巨人的肩膀上,两位大神sunnyxx、雷纯锋早在14、15年就已经做了autorelease的实验。
但是他们都提到了,在一些新的设备上,实验结果不再成立
但我发现,其实不是结果不成立,而是另有原因。
我们重复一下实现来看看。
实验很简单,声明一个全局weak变量reference
来指向一个临时变量,为的是利用weak不影响对象的生命周期的特性,来探究该临时变量何时释放。
代码如下:
__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [NSString stringWithFormat:@"reference"];
reference = str;
NSLog(@"viewDidLoad reference: %@", reference);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"viewWillAppear reference: %@", reference);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear reference: %@", reference);
}
复制代码
我在不同机型上,做了测试,打印结果如下:
- iPhone 5, iOS 8.3
2018-05-20 17:15:49.477 AutoreleasePool[34551:718353] viewDidLoad reference: reference
2018-05-20 17:15:49.478 AutoreleasePool[34551:718353] viewWillAppear reference: reference
2018-05-20 17:15:49.511 AutoreleasePool[34551:718353] viewDidAppear reference: (null)
复制代码
- iPhone 8, iOS 11.3
2018-05-20 17:17:17.514862+0800 AutoreleasePool[34599:721398] viewDidLoad reference: reference
2018-05-20 17:17:17.515135+0800 AutoreleasePool[34599:721398] viewWillAppear reference: reference
2018-05-20 17:17:17.521916+0800 AutoreleasePool[34599:721398] viewDidAppear reference: reference
复制代码
从实验结果可以看到:
- 在老设备上,一个临时变量,出了他所在的栈,没有立刻释放,而是到了viewDidAppear的时候,才真正释放。这验证了autoreleasePool延长了一个对象的生命周期。
- 而在新的设备上,viewDidAppear中reference并没有释放掉
两位大神都没有解释原因。
1.2 进一步测试
1.2.1 新建vc
在大神的基础上,我希望知道为啥reference没有释放掉。
思路是:新建一个新的viewcontroller,这样可以push、pop,在vc被pop的时候,观察reference,看他具体什么时候销毁的。
于是我新建了一个新的viewcontroller,命名为NextViewcontroller
。代码同viewcontroller,只是weak的全局变量命名为nextReference
,以做区别。
打印log如下:
2018-05-20 17:30:22.747640+0800 AutoreleasePool[35007:744443] viewDidLoad reference: reference
2018-05-20 17:30:22.748219+0800 AutoreleasePool[35007:744443] viewWillAppear reference: reference
2018-05-20 17:30:22.755195+0800 AutoreleasePool[35007:744443] viewDidAppear reference: reference
2018-05-20 17:30:23.879561+0800 AutoreleasePool[35007:744443] jumpToNext
2018-05-20 17:30:23.883701+0800 AutoreleasePool[35007:744443] viewDidLoad nextReference: nextReference
2018-05-20 17:30:23.883960+0800 AutoreleasePool[35007:744443] viewWillAppear nextReference: nextReference
2018-05-20 17:30:24.415991+0800 AutoreleasePool[35007:744443] viewDidAppear nextReference: (null)
复制代码
可以看到,在ViewController中,reference依然无法释放,NextViewController却释放了!!
这就是说跟设备无关了,因为这是在同一个设备,几乎同时做的测试。
1.2.2 改变加载顺序
我怀疑是否是加载顺序的问题导致的呢?
按流程是先加载ViewController,再跳转到NextViewController
于是我打算,先加载NextViewController,再跳转到ViewController
于是需要修改AppDelegate、NextViewController代码,如下:
// AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIWindow *window = [[UIWindow alloc] init];
NextViewController *vc = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:vc];
window.rootViewController = navi;
[window makeKeyAndVisible];
self.window = window;
return YES;
}
// NextViewController
- (IBAction)junmToVC:(id)sender {
NSLog(@"junmToVC");
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *vc = (UIViewController *)[storyboard instantiateViewControllerWithIdentifier:@"ViewController"];
[self.navigationController pushViewController:vc animated:YES];
}
复制代码
打印log如下:
2018-05-20 17:39:49.081968+0800 AutoreleasePool[35276:763190] viewDidLoad nextReference: nextReference
2018-05-20 17:39:49.082502+0800 AutoreleasePool[35276:763190] viewWillAppear nextReference: nextReference
2018-05-20 17:39:49.088283+0800 AutoreleasePool[35276:763190] viewDidAppear nextReference: (null)
2018-05-20 17:39:50.364852+0800 AutoreleasePool[35276:763190] junmToVC
2018-05-20 17:39:50.373906+0800 AutoreleasePool[35276:763190] viewDidLoad reference: reference
2018-05-20 17:39:50.374281+0800 AutoreleasePool[35276:763190] viewWillAppear reference: reference
2018-05-20 17:39:50.910014+0800 AutoreleasePool[35276:763190] viewDidAppear reference: reference
复制代码
结果依然是nextReference可以释放,reference无法释放。
那么应该跟加载顺序无关了
1.2.3 UIStoryboard
如果还有什么区别,那么就是一个是从UIStoryboard
加载的,一个是从xib文件加载的
为了验证,我再新建一个vc在main.storyboard中,命名为StSecViewController
。
打印log如下:
2018-05-20 17:52:04.201883+0800 AutoreleasePool[35699:786345] viewDidLoad nextReference: nextReference
2018-05-20 17:52:04.202527+0800 AutoreleasePool[35699:786345] viewWillAppear nextReference: nextReference
2018-05-20 17:52:04.210418+0800 AutoreleasePool[35699:786345] viewDidAppear nextReference: (null)
2018-05-20 17:52:29.948263+0800 AutoreleasePool[35699:786345] junmToSecVC
2018-05-20 17:52:29.955107+0800 AutoreleasePool[35699:786345] viewDidLoad StSecReference: StSecReference
2018-05-20 17:52:29.955354+0800 AutoreleasePool[35699:786345] viewWillAppear StSecReference: StSecReference
2018-05-20 17:52:30.487475+0800 AutoreleasePool[35699:786345] viewDidAppear StSecReference: (null)
复制代码
可以看到nextReference
和StSecReference
都释放了,也就是说跟UIStoryboard也无关。
疑惑
目前看起来好像是只有xcode新建工程,默认的ViewController中的临时变量无法释放
这是为啥呢?
疑惑二:UIKit做了什么
2.1 NSArray
AutoreleasePool一个常用的场景就是在for循环时,避免生成过多的临时变量,抢占内存
for (int i = 0; i < 99999; i++) {
@autoreleasepool {
id obj = [NSArray arrayWithObjects:@(1), nil];
NSLog(@"%@",obj);
}
}
复制代码
内存使用情况,分别如如下:
使用AutoreleasePool:
不使用AutoreleasePool:
可以看得出来,不使用会导致内存使用一直高涨。
2.2 UIKit
2.2.1 UIButton
刚才是实验中,使用的是NSArray,我们换成UIButton试试看
for (int i = 0; i < 99999; i++) {
@autoreleasepool {
// id obj = [NSArray arrayWithObjects:@(1), nil];
id obj = [[UIButton alloc] init];
NSLog(@"%@",obj);
}
}
复制代码
效果如截图:
使用AutoreleasePool
不使用AutoreleasePool:
奇怪的是:
- AutoreleasePool对NSArray效果显著,不使用的事情下有显著增长
- 对UIButton却没有显著效果,即使使用了,内存还是会涨,但是峰值在73M,不使用则达到82.5M
一个可能的猜测是来自官方文档
The AppKit and UIKit frameworks process each event-loop iteration (such as a mouse down event or a tap) within an autorelease pool block. Therefore you typically do not have to create an autorelease pool block yourself, or even see the code that is used to create one
难道是UIKit自己维护了autoreleasePool?
哪位大神可以解释一下,多谢!