原帖地址:http://www.padovo.com/blog/2012/11/13/study-arc-note/
这一篇很有价值的关于ARC的博文,我写的。 花了我一晚上,把之前关于ARC的所有不明白和疑惑的地方都记了个遍。 希望你们能喜欢,同样,转载的话,请附上本方的网址。
这里的博客大都是原创,看到这里博客觉得好的,可能给我个工作机会的,可以发Email给我:kutzhang@gmail.com
ARC是iOS 5引进的东西,而我一直在用这个东西,但是还是不确定地使用这个东西,因为,它有一堆疑问让自己很蛋疼。相比之下,自己手工管理内存或许会更好,但是新技术的出现也说明它将来的重要性,所以也就强制自己使用ARC。
这里有一份教程
我这里只写疑问和解决心得,不写基本的东西。
weak真的就在所指对象的引用计数为0后就自动nil吗?
实践证明,肯定不是!所以教程说的是错的!!!
这里是实践代码:
BWPerson12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
//// BWPerson.h// CoreARC//// Created by kut.zhang on 11/13/12.// Copyright (c) 2012 kut.zhang. All rights reserved.//#import <Foundation/Foundation.h>@interface BWPerson : NSObject@property (copy) NSString *name;- (id)initWithName:(NSString *)name;@end//// BWPerson.m// CoreARC//// Created by kut.zhang on 11/13/12.// Copyright (c) 2012 kut.zhang. All rights reserved.//#import "BWPerson.h"@implementation BWPerson- (id)initWithName:(NSString *)name{ self = [super init]; if (self) { self->_name = name; } return self;}- (NSString *)description{ return self.name;}- (void)dealloc{ NSLog(@"person dealloced");}@end
main.m123456789101112131415161718192021222324252627282930313233343536373839
//// main.m// CoreARC//// Created by kut.zhang on 11/13/12.// Copyright (c) 2012 kut.zhang. All rights reserved.//#import <Foundation/Foundation.h>#import "BWPerson.h"// 测试weak引用void testWeak(void);int main(int argc, const char * argv[]){ @autoreleasepool { testWeak(); NSLog(@"autorelease pool exiting."); } NSLog(@"autorelease pool exited."); NSLog(@"program exited"); return 0;}void testWeak(void){ BWPerson *person = [[BWPerson alloc] initWithName:@"kut"]; __weak BWPerson *personWeak = person; NSLog(@"strong: %@", person); NSLog(@"weak: %@", personWeak); person = nil; NSLog(@"strong: %@", person); NSLog(@"weak: %@", personWeak);}
输出结果:
main.m12345678
2012-11-13 05:00:11.101 CoreARC[32938:303] strong: kut2012-11-13 05:00:11.103 CoreARC[32938:303] weak: kut2012-11-13 05:00:11.103 CoreARC[32938:303] strong: (null)2012-11-13 05:00:11.104 CoreARC[32938:303] weak: kut2012-11-13 05:00:11.104 CoreARC[32938:303] autorelease pool exiting.2012-11-13 05:00:11.105 CoreARC[32938:303] person dealloced2012-11-13 05:00:11.105 CoreARC[32938:303] autorelease pool exited.2012-11-13 05:00:11.105 CoreARC[32938:303] program exited
看到没,我可怜的教程说的不对,我都把strong的那个引用设为nil了,这该死的weak引用的对象还是会输出老子的英文名,所以,教程是骗人的。 哦,不,不能这样说,应该说是:
教程只是说了一些“对的“,没说为什么这是”对的“,其实另有隐情。
可以观察一下输出,在后半部份,在退出autorelease pool的时候,我的person的释构函数被神奇地调用了,而不是在strong引用被赋值为nil的时候,于是乎我要求证一个问题,如果没有weak引用它呢?会是什么样的一个情况?我改了一下代码,去掉weak:
123456789
void testWeak(void){ BWPerson *person = [[BWPerson alloc] initWithName:@"kut"]; NSLog(@"strong: %@", person); person = nil; NSLog(@"strong: %@", person);}
输出结果:
123456
2012-11-13 05:11:47.194 CoreARC[32969:303] strong: kut2012-11-13 05:11:47.196 CoreARC[32969:303] person dealloced2012-11-13 05:11:47.197 CoreARC[32969:303] strong: (null)2012-11-13 05:11:47.197 CoreARC[32969:303] autorelease pool exiting.2012-11-13 05:11:47.197 CoreARC[32969:303] autorelease pool exited.2012-11-13 05:11:47.198 CoreARC[32969:303] program exited
看到没,这strong引用被赋值为nil后立即就调用了释构函数,和之前的那段代码的输出完全不同。难道weak的出现,导致了person释放的时机变了?由之前的代码可以估计,这个person是autorelease pool给release掉的,而上面的代码则是自己干掉自己的。也就是说,weak的出现,导致了person变成了autorelease了?我又要求证一下:
12345678910111213141516171819
void testWeak(void){ BWPerson *person; __weak BWPerson *personWeak; @autoreleasepool { person = [[BWPerson alloc] initWithName:@"kut"]; personWeak = person; NSLog(@"strong: %@", person); NSLog(@"weak: %@", personWeak); person = nil; NSLog(@"inner autorelease pool exiting...."); } NSLog(@"inner autorelease pool exited"); NSLog(@"strong: %@", person); NSLog(@"weak: %@", personWeak);}
输出结果:
12345678910
2012-11-13 05:19:13.374 CoreARC[32994:303] strong: kut2012-11-13 05:19:13.376 CoreARC[32994:303] weak: kut2012-11-13 05:19:13.377 CoreARC[32994:303] inner autorelease pool exiting....2012-11-13 05:19:13.377 CoreARC[32994:303] person dealloced2012-11-13 05:19:13.378 CoreARC[32994:303] inner autorelease pool exited2012-11-13 05:19:13.378 CoreARC[32994:303] strong: (null)2012-11-13 05:19:13.378 CoreARC[32994:303] weak: (null)2012-11-13 05:19:13.379 CoreARC[32994:303] autorelease pool exiting.2012-11-13 05:19:13.379 CoreARC[32994:303] autorelease pool exited.2012-11-13 05:19:13.379 CoreARC[32994:303] program exited
事实证明了一切,教程是”对的“,只是,必须得打上双引号。
可以总结一下,weak引用会导致所引用的对象autorelease,所以就算strong引用设为了nil,这个对象还是存在的,并没有被释构。而在autorelease之后,weak引用就会被赋为nil,所以@autoreleasepool和以前的autoreleasepool不一样了,它还负责weak的nil赋值。
那么这样是有问题的,autorelease的时机是什么?如果是main方法里的话,那么就会出现内存泄漏,因为直到@autoreleasaepool块执行完毕之后,这些内存才会被释放,这和泄漏已经没什么两样了。所以,合适的时候就尽可能的在controller代码里加入@autoreleasepool块,及时清理内存。
局部引用在函数内部的生命周期又是怎么样的?
还是上代码:
1234567
void testScope(void){ BWPerson *person = [[BWPerson alloc] initWithName:@"kut"]; NSLog(@"%@", person); NSLog(@"function exiting.......");}
输出是:
123456
2012-11-13 05:32:50.713 CoreARC[33034:303] kut2012-11-13 05:32:50.715 CoreARC[33034:303] function exiting.......2012-11-13 05:32:50.715 CoreARC[33034:303] person dealloced2012-11-13 05:32:50.716 CoreARC[33034:303] autorelease pool exiting.2012-11-13 05:32:50.716 CoreARC[33034:303] autorelease pool exited.2012-11-13 05:32:50.716 CoreARC[33034:303] program exited
这次发现,strong引用过了函数的scope之后,就立马释构了。嗯,等等,如果这个testScope是一个”创建者“的角色呢?也就是说,它返回这个person,那么这个对象的生命周期又是怎么样的呢?还是要求证一下:
12345678910111213141516171819202122
BWPerson *testScope(void){ BWPerson *person = [[BWPerson alloc] initWithName:@"kut"]; NSLog(@"%@", person); NSLog(@"function exiting......."); return person;}int main(int argc, const char * argv[]){ @autoreleasepool { BWPerson *person = testScope(); NSLog(@"after testScope: %@", person); NSLog(@"autorelease pool exiting."); } NSLog(@"autorelease pool exited."); NSLog(@"program exited"); return 0;}
输出是:
1234567
2012-11-13 05:38:47.149 CoreARC[33053:303] kut2012-11-13 05:38:47.151 CoreARC[33053:303] function exiting.......2012-11-13 05:38:47.151 CoreARC[33053:303] after testScope: kut2012-11-13 05:38:47.151 CoreARC[33053:303] autorelease pool exiting.2012-11-13 05:38:47.152 CoreARC[33053:303] person dealloced2012-11-13 05:38:47.152 CoreARC[33053:303] autorelease pool exited.2012-11-13 05:38:47.153 CoreARC[33053:303] program exited
很明显,输出和之前不一样,但肯定确定的一个事情就是,函数执行完后对象依然没有被释构,并且被释构的时机是在autoreleasepool这里。
这样就可以明白: 在函数里的局部对象,如果不是返回对象的话,那么它的生命周期只在函数内部,也就是说,它是strong的。如果是返回对象的话,那么,它的生命周期是直到外层的autoreleasepool释放,也就是说,它不是strong的,它是__autoreleasing的。
所以说,教程也是很蛋疼的,它竟然说要在函数内部把要返回的对象__autoreleasing申明一下。很明显,这是多余的,LLVM会帮我们干这个,不过我们必须要知道,LLVM的确为我们干了这个。
对象里的字段(注意,不是property)的ARC又是怎么样的呢?
Apple说,最好不要在init方法里使用property初始化,而应该用使用字段,就像如下那样:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
//// BWTeam.h// CoreARC//// Created by kut.zhang on 11/13/12.// Copyright (c) 2012 kut.zhang. All rights reserved.//#import <Foundation/Foundation.h>#import "BWPerson.h"@interface BWTeam : NSObject@property (strong) NSString *name;@property (strong) BWPerson *leader;- (id)initWithName:(NSString *)name leader:(BWPerson *)leader;@end//// BWTeam.m// CoreARC//// Created by kut.zhang on 11/13/12.// Copyright (c) 2012 kut.zhang. All rights reserved.//#import "BWTeam.h"@implementation BWTeam- (id)initWithName:(NSString *)name leader:(BWPerson *)leader{ self = [super init]; if (self) { _name = name; _leader = leader; } return self;}- (NSString *)description{ return [NSString stringWithFormat:@"team: %@", _name];}- (void)dealloc{ NSLog(@"Team dealloced");}@end
当我在init方法里给私有字段赋值的时候我心里很没底,这些个赋值对象会不会自动retain一次呢?
我再写一些代码做一次试验:
12345678910
void testObjectFieldScope(void){ BWPerson *leader = [[BWPerson alloc] initWithName:@"kut"]; NSString *name = @"NoJobTeam"; BWTeam *team = [[BWTeam alloc] initWithName:name leader:leader]; NSLog(@"%@", team); leader = nil; NSLog(@"function exiting.........");}
输出是:
1234567
2012-11-13 06:10:34.433 CoreARC[33133:303] team: NoJobTeam2012-11-13 06:10:34.434 CoreARC[33133:303] function exiting.........2012-11-13 06:10:34.435 CoreARC[33133:303] Team dealloced2012-11-13 06:10:34.435 CoreARC[33133:303] person dealloced2012-11-13 06:10:34.436 CoreARC[33133:303] autorelease pool exiting.2012-11-13 06:10:34.436 CoreARC[33133:303] autorelease pool exited.2012-11-13 06:10:34.436 CoreARC[33133:303] program exited
可以看到,person对象被赋值给team对象,在team将要离开函数作用域之时,它会先释构自己(从前面的笔记中可以了解),然后释构自己肚子里的所有私有字段。也就是说,只要team存在,person就会存在,也就证明,在team肚子里的私有对象字段都是strong的。
至于@property的话,嗯,有了上面这个证明,也就不难理解了。当然,如果@property申明字段是weak的话,它和函数里weak是一样的,赋值nil的时机就是autoreleasepool块结束。
所以,没有太必要,就不要写dealloc方法,因为LLVM会给老子做老子想做的事情。
那么__autoreleasing用在哪里?
这是个问题,那么我们再想另一个问题:如果我们不用get方法或者生成方法返回对象,而用蛋疼的指针呢? 比如:
12345
void generatePerson(BWPerson **p2p);BWPerson *person = nil;generatePerson(person);NSLog(@"%@", person);
很明显这语法从逻辑上意思是,我给一个id的地址给你,你把这个地址写上id值就可以了。可是,很明显,p2p所指的内存只是个函数局部变量(在定义p2p时只是定义了p2p是strong的,却完全没有定义它指向的内存是什么属性的,默认是assign,所以……),一过函数范围就立马被释放。唯一不被释放的方法就是,让p2p所指的那块内存不是局部的,而是由上层的autoreleasepool处理的。这时__autoreleasing就派得上用场了。
其实上面的代码经过LLVM之后,得到的代码是这样的:
1234567
void generatePerson((BWPerson * __autoreleasing *)p2p);BWPerson *person = nil;BWPerson __autoreleasing *tmp = person;generatePerson(&tmp);person = tmp;NSLog(@"%@", person);
这个议题真的比较难懂,大概脑子里有个大概,不过我们完全可以忽略掉它,因为这些东西完全给LLVM给隐藏掉了,我们大方用就是了。
循环引用
如教程所说的,对象与对象之间有父子关系的话,如果一味使用strong引用的话,就会产生循环引用。
预防循环引用最好的方法就是,子对象对父对象进行weak引用。如果父子对象关系并不明显,如modal controller,那么就要思考,或者也叫估计,看谁的命长,命长的那个可以strong引用命短的那个,命短的那个则是weak引用命长的那个。当然,modal contorller里,命长的那个并没有引用命短的那个,这里有个让人难懂的问题,后面说。
在教程最后的那部份里,关于blocks的循环引用,方法总结为:
1.要么block里不引用自己赋值予的对象,要么就不要把这个引用了自己的block赋给自己。
2.另一种(这种蛋疼的方式很有问题):
12
1)先临时把主对象赋给一个weak对象,2)blocks操纵的是weak对象,不过要先检测这个weak对象是不是nil,当然,一般都不会为nil,因为在方法里,如果一旦有weak引用到strong对象的话,这个strong对象会被改为__autoreleasing对象(貌似会变成nil的可能性相当大,因为block都是异步执行,可能执行的时候方法已经退出,这时如果外层是一个@autoreleasepool块的话,weak对象就为nil了)。
关于modal controller的生命周期
一般情况下我们会在action里做这样的事情:
123
BWDetailController *targetContorller = [[BWDetailController alloc] init];targetController.delegate = self;[self presentViewController:targetContorller animated:YES completion:nil];
很明显,targetController是一个局部strong变量,一过action方法后就立马release掉,可是事实是,这家伙不但没有被release掉,反而活得好好的。那么是什么保持了这个controller了呢?使得它的生命周期超过了action方法了呢?
其实是action所在的controller持有了这个target controller(由presentingViewController属性持有)。在执行presentViewController
时就做了手脚。
注,view里引用controller的是weak引用。所以,在添加子controller的时候,一定要在主controller里持有子controller,不然的话子controller view里的控件事件会因为子controller的提前release而报错。
__bridge,C指针与ObjC对象转型
这个东西一下子让我很难理解,也是自己一直很模糊的概念,我一直都不明白id和C指针之间的关系是什么,只是模糊知道,应该是一样的,而实践上,也确实是一样的。在一些常与Core Graphics有关的代码中常用到这个:
1
imageLayer.contents = (__bridge id)([UIImage imageNamed:imageName2].CGImage);
__bridge这东西就是一个转换用的东西,就是id与指针之间的转换,不过加入了retain count这样的概念。也就是,你把一个指针转成id的时候,当你把id release同时retain count == 0的时候,其实就等于free(p)。这样就好理解了。
既然是加入retain count的概念,那么就把它一一对应上吧: assign: bridge retain: bridge_retained retain and release: __bridge_transfer
下面详解一下?列例子吧,教程已经说的很好了:
assign: __bridge:就是assign赋值,没有retain count++
12345
void *p = ...;id obj = (__bridge id)p;// 等同于:id obj = (id)p;
retain: __bridge_retained:就是retain赋值,retain count++
123456
void *p = ...;id obj = (__bridge_retained id)p;// 等同于:id obj = (id)p;[obj retain];
retain and release: __bridge_transfer:就是先retain赋值,然后release自己。
1234567
void *p = ...;id obj = (_bridge_transfer id)p;// 等同于:id obj = (id)p;[obj retain];[(id)p release];
这个教程后半部份讲了Core Foundation相关的东西,讲的很详细,这里就不写了。
其实还有些东西不能理解的,比如说:retain property:
1
imageLayer.contents = (__bridge id)([UIImage imageNamed:imageName2].CGImage);
contents是一个retain的property,为什么是用__bridge
而不是__bridge_retained
或者__bridge_transfer
呢?这个东西有点难以理解,因为contents是retain的property,那么,这个赋值过程就是:
123
[value retain];[_contents release];_contents = value;
要知道,property不是变量,它是方法,所以,不能按变量的方式来理解,那么LLVM在这样的代码里为我们做了什么呢?这就不得而知,只是知道,把上面的代码改成__bridge_transfer
的话,程序会崩溃,如果改成__bridge_retained
的话,则编译通不过。
所以,这个方面的事情先放一下,以后有机会或者时间,再详细了解一下了。
总结:
终于看完,虽然最后一部份并不是完全理解,但是在自己的脑子里,ARC已经可以在大部份代码熟练应用了。
完成这篇日志,收工。