ARC学习笔记

来源:互联网 发布:暖脚宝哪个牌子好 知乎 编辑:程序博客网 时间:2024/05/22 00:14


原帖地址:http://www.padovo.com/blog/2012/11/13/study-arc-note/

这一篇很有价值的关于ARC的博文,我写的。 花了我一晚上,把之前关于ARC的所有不明白和疑惑的地方都记了个遍。 希望你们能喜欢,同样,转载的话,请附上本方的网址。

这里的博客大都是原创,看到这里博客觉得好的,可能给我个工作机会的,可以发Email给我:kutzhang@gmail.com

ARC是iOS 5引进的东西,而我一直在用这个东西,但是还是不确定地使用这个东西,因为,它有一堆疑问让自己很蛋疼。相比之下,自己手工管理内存或许会更好,但是新技术的出现也说明它将来的重要性,所以也就强制自己使用ARC。

这里有一份教程

我这里只写疑问和解决心得,不写基本的东西。

weak真的就在所指对象的引用计数为0后就自动nil吗?

实践证明,肯定不是!所以教程说的是错的!!!

这里是实践代码:

BWPerson
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
////  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.m
123456789101112131415161718192021222324252627282930313233343536373839
////  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.m
12345678
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已经可以在大部份代码熟练应用了。

完成这篇日志,收工。

原创粉丝点击