AutoreleasePool 分析

来源:互联网 发布:淘宝详情页背景素材 编辑:程序博客网 时间:2024/04/28 11:49

前言

AutoreleasePool自动释放池,对于自动释放对象的作用如何?
释放池中的自动释放对象什么时候会被释放?

MRC环境下

场景1

NSString *string_var_ = nil;- (void)viewDidLoad {    [super viewDidLoad];    NSString *string = [NSString stringWithFormat:@"bluefish"];    string_var_ = string;    NSLog(@"viewDidLoad--string: %@", string);}- (void)viewWillAppear:(BOOL)animated {    [super viewWillAppear:animated];    NSLog(@"viewWillAppear--string: %@", string_var_);}- (void)viewDidAppear:(BOOL)animated {    [super viewDidAppear:animated];    NSLog(@"viewDidAppear--string: %@", string_var_);}

控制台打印:

viewDidLoad--string: bluefishviewWillAppear--string: bluefish

然后,NSLog(@"viewDidAppear--string: %@", string_var_);
野指针报错。
其中,NSString *string = [NSString stringWithFormat:@"bluefish"];是自动释放对象,引用计数为1,string对其引用,在viewDidLoad和viewWillAppear返回时,string对象还没有被release,到viewDidAppear时被release了,也就是说string对象在viewWillAppear和viewDidAppear之间被线程的自动释放池release。

那如果string是非自动释放的呢,

NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];

则三个方法都会打印,但因为string一直没有显式调用relase方法,造成string说引用的[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一直没释放,不符合手动内存管理原则。

场景 2

如果显式使用autoreleasepool自动释放池,代码:

- (void)viewDidLoad {@autoreleasepool {        NSString *string = [NSString stringWithFormat:@"bluefish"];        string_var_ = string;    }NSLog(@"viewDidLoad--string: %@", string_var_);}

执行后,NSLog(@"viewDidLoad--string: %@", string_var_);野指针报错。
说明,autorelease创建时(引用计数为1),会自动加入到autoreleasepool{}自动释放池里,结束时,会把里面的自动释放对象作一次release(引用计数-1),所以string_var_成了野指针,而局部变量string作用域结束被销毁(出栈),因此log打印时,string_var_野指针报错。

那autoreleasepool里的非autorelease对象会不会被释放呢,我们试试:

@autoreleasepool {        NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];        string_var_ = string;    }

结果是三个方法都打印出来了,说明,非autorelease对象不会被加入autoreleasepool。其情况就跟 “场景1” 一样,string一直得不到释放。

场景3

如果显式使用autoreleasepool自动释放池,而且将string定义在autoreleasepool{}外会怎么样,如下

- (void)viewDidLoad {    [super viewDidLoad];     NSString *string = nil;    @autoreleasepool {        string = [NSString stringWithFormat:@"bluefish"];        string_var_ = string;    }    NSLog(@"viewDidLoad--string: %@", string_var_);}

运行后,情况跟“场景2”一样,无法成功打印,野指针报错,因为[NSString stringWithFormat:@"bluefish"]为自动释放对象,string对其做引用,一旦出了autoreleasepool{},string所指向的[NSString stringWithFormat:@"bluefish"]便会被释放掉,string和string_var_都成了野指针。
那如果换成[[NSString alloc] initWithFormat:@"%@",@"bluefish"]会怎么,答案是跟“场景2”一样,对象引用一直有效,因为一直没有release。

小结:在一般情况下,创建的autorelease对象会被加入到离自己最近的释放池,如果没显式使用autoreleasepool{},则加入到当前线程的释放池中。一旦释放池结束,里面的对象都会做一次release。
注意即使显式地使用autoreleasepool{},里面的非自动释放对象也要手动release,否则对象一直不会释放。

以上是MRC(手动内存管理)下,自动释放池autoreleasepool对对象的处理差异。但在ARC下会如何呢。

ARC环境下

场景1

这里我们使用_weak修饰外包变量string_weak,根据arc的规则,string_weak_不对他引用的对象做持有,不会影响引用的对象的释放,当所应用对象被释放时,string_weak_会自动被设为nil,所以这里用弱引用变量string_weak_来观察对象的释放,下面看代码:

__weak NSString *string_weak_ = nil;- (void)viewDidLoad {    [super viewDidLoad];    // 场景 1    NSString *string = [NSString stringWithFormat:@"bluefish"];    string_weak_ = string;    NSLog(@"viewDidLoad--string: %@", string_weak_);}- (void)viewWillAppear:(BOOL)animated {    [super viewWillAppear:animated];    NSLog(@"viewWillAppear--string: %@", string_weak_);}- (void)viewDidAppear:(BOOL)animated {    [super viewDidAppear:animated];    NSLog(@"viewDidAppear--string: %@", string_weak_);}

执行后控制台输入:

viewDidLoad--string: bluefishviewWillAppear--string: bluefishviewDidAppear--string: (null)

从打印接口可看string对象在viewWillAppear和viewDidAppear之间被释放。这个释放时机与MRC下是一样的,只不过MRC下变成野指针报错而已。但理解方式就不一样了。
[NSString stringWithFormat:@"bluefish"]创建时,被自动加入所在线程的自动释放池中,这样,它的释放就交给了释放池去管理了。然后,string对其做引用,因为arc下,默认是_strong,所以string对其做了一次强引用。而当string出了viewDidLoad方法,局部变量string就会被销毁,这时,string对[NSString stringWithFormat:@"bluefish"]的强引用就消失了,但是因为[NSString stringWithFormat:@"bluefish"]还在自动释放池中未被释放,所以string_weak还可以引用到它,直到当前线程自动释放池释放[NSString stringWithFormat:@"bluefish"]

那么如果是非自动释放对象呢:

NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];

执行打印结果:

 viewDidLoad--string: bluefish viewWillAppear--string: (null) viewDidAppear--string: (null)

结果显示[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一离开viewDidLoad方法,就被释放了。因为,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]为非自动释放对象,创建时,不会被加入自动释放池中,所以它的释放是在arc机制下的,当强引用对象string离开作用域时,被销毁,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]失去所有的强引用,没有任何一个强引用对象指向它,因此arc对其进行了release。

场景2

显式使用autoreleasepool自动释放池,代码:

- (void)viewDidLoad {    [super viewDidLoad];    @autoreleasepool {        NSString *string = [NSString  stringWithFormat:@"bluefish"];        string_weak_ = string;    }    NSLog(@"viewDidLoad--string: %@", string_weak_);}

执行打印结果:

 viewDidLoad--string: (null) viewWillAppear--string: (null) viewDidAppear--string: (null)

从结果看,[NSString stringWithFormat:@"bluefish"]一出autoreleasepool{}就被释放了。分析一下,
跟“场景1”一样,[NSString stringWithFormat:@"bluefish"]创建时,被自动加入autoreleasepool{}自动释放池中,这样,它的释放就交给了释放池去管理了。然后,string对其做强引用,而且string是在autoreleasepool{}中定义的,当autoreleasepool{}结束时,string被销毁,强引用消失,而这时,autoreleasepool{}自动释放池也结束了,因此[NSString stringWithFormat:@"bluefish"]也被释放掉。

那如果换成非自动释放对象呢,

 @autoreleasepool {        NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];        string_weak_ = string;    }

打印结果还是一样,why?下面分析,
因为[[NSString alloc] initWithFormat:@"%@",@"bluefish"]非自动释放,所以他不会加入到autoreleasepool,而是靠arc来管理内存,string对其作强引用。但autoreleasepool{}结束时,局部变量string销毁,而arc下,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一旦失去了任何强引用,也会被释放。所以,最后一出autoreleasepool{}就都被释放了。

PS:可以使用lldb指令 watchpoint set v string_weak_ 设置观察点,观察 string_weak_ 变量的值的变化。先设置一个断点,然后设置watchpoint set v string_weak_ ,继续执行程序,如果string_weak_改变(变为nil),就会被检测到。

watchpoint set v string_weak_Watchpoint created: Watchpoint 1: addr = 0x000978e0 size = 4 state = enabled type = w    declare @ '/Users/Bluefish/Documents/textPro/AutoreleasePoolTest/AutoreleasePoolTest/ViewController.m:15'    watchpoint spec = 'string_weak_'    new value: 0x7a160340Watchpoint 1 hit:old value: 0x7a160340new value: 0x00000000

场景3

显式使用autoreleasepool自动释放池,而且将string定义在autoreleasepool{}外,如下

- (void)viewDidLoad {    [super viewDidLoad];    NSString *string = nil;    @autoreleasepool {        string = [NSString stringWithFormat:@"bluefish"];        string_weak_ = string;    }    NSLog(@"viewDidLoad--string: %@", string_weak_);}

执行结果:

viewDidLoad--string: bluefishviewWillAppear--string: (null)viewDidAppear--string: (null)

你可能会奇怪为什么出了autoreleasepool{}后,还是可以打印出来,[NSString stringWithFormat:@"bluefish"]没被释放掉。
下面分析,
[NSString stringWithFormat:@"bluefish"]创建时,被加入autoreleasepool释放池,string对其强引用,当autoreleasepool{}结束时,[NSString stringWithFormat:@"bluefish"]被释放,但是,string还在其作用域内,因此还继续对[NSString stringWithFormat:@"bluefish"]做强引用,所以不会被释放,而直到viewDidLoad{}结束,string被销毁,强引用消失,在arc下,失去了任何强引用的[NSString stringWithFormat:@"bluefish"]最终被释放掉。

那如果是非自动释放对象呢,如下:

NSString *string = nil;    @autoreleasepool {        string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];        string_weak_ = string;    }

执行打印结果也是一样。在这里autoreleasepool{}自动释放池其实起不到什么实际作用。下面分析,
[[NSString alloc] initWithFormat:@"%@",@"bluefish"]创建时,因为其不是自动释放对象,所以并不会加入到autoreleasepool的自动释放池中,也就是他的内存管理还是由arc来处理,string对其强引用,当autoreleasepool{}结束时,释放池中没有可释放的对象,而string也还在其作用域内,所以,autoreleasepool{}什么也没做,到viewDidLoad{}结束时,string销毁,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]失去任何强引用,最终都被释放。

小结:在arc环境下,autoreleasepool{}同样也只对自动释放对象做管理,当autoreleasepool{}结束时,自动释放池中的对象。但因为arc机制的存在,autoreleasepool{}的功能其实可以当成是被弱化的,它的作用更多的是用于在arc环境下,控制自动对象的释放时机,如“场景2”。

总结

只有自动释放对象会被加入离它最近的自动释放池中,自动释放池结束时,池中的对象都会被释放一次。
局部变量,在其作用域结束后,就会被销毁(出栈)。

一般情况下,在程序中的自动释放对象(非显式使用alloc new copy或者MRC下使用autorelease)会被加入到当前线程的自动释放池中(每个线程都会有一个默认的自动释放池),当这线程结束时,其自动释放池中的对象被销毁,参考“场景1”。所以,当你没显式使用autoreleasepool{}的时候,程序中的自动释放对象依然是在一个隐式的自动释放池中(你的程序都是运行中各个线程中的,包括主线程)。

在MRC下,是通过引用计数的概念来管理内存。一个对象一旦被创建其引用计数即为1,创建后你可以使用其他普通的对象指针变量来指向它,跟arc不同是指向它不会改变这个对象的引用计数,除非向对象发送retain或者copy(浅拷贝)计数+1,创建出来的对象,如果是通过alloc new copy创建出来的,除了自动释放对象最后都要向对象发送release方法。
所以,最后,只要记得一条准则,在MRC下,在autoreleasepool{}中创建的非自动释放对象都要调用release方法。而autoreleasepool{}创建的自动释放对象,一旦autoreleasepool结束就会被release一次,不管其之前被哪个对象引用过。

在ARC下,内存管理以对象强弱引用来处理,一个对象一旦失去所有强引用,就会被销毁,其弱引用会被自动设为nil。
在ARC下,创建出来的对象可以视为两种不同的内存处理模式。一种是非自动释放对象,其内存管理由arc来处理。一种是自动释放对象,其内存管理由自动释放池来出来。
同理,在autoreleasepool{}中创建的非自动对象由arc来处理,自动释放对象由当前autoreleasepool来管理,但由于arc的环境下,不管在autoreleasepool{}中创建的非自动释放对象还是自动释放对象,最后都会被释放一次(因为autoreleasepool{}中的非自动释放对象会在该作用域范围结束时被arc释放一次)。
而如果在autoreleasepool{}结束时,有作用域在该autoreleasepool{}外的变量对里面创建的对象做强引用时,该对象继续保留。

0 0
原创粉丝点击