原帖地址: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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| //
// 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| //
// 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
1
2
3
4
5
6
7
8
| 2012-11-13 05:00:11.101 CoreARC[32938:303] strong: kut
2012-11-13 05:00:11.103 CoreARC[32938:303] weak: kut
2012-11-13 05:00:11.103 CoreARC[32938:303] strong: (null)
2012-11-13 05:00:11.104 CoreARC[32938:303] weak: kut
2012-11-13 05:00:11.104 CoreARC[32938:303] autorelease pool exiting.
2012-11-13 05:00:11.105 CoreARC[32938:303] person dealloced
2012-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:
1
2
3
4
5
6
7
8
9
| void testWeak(void)
{
BWPerson *person = [[BWPerson alloc] initWithName:@"kut"];
NSLog(@"strong: %@", person);
person = nil;
NSLog(@"strong: %@", person);
}
|
输出结果:
1
2
3
4
5
6
| 2012-11-13 05:11:47.194 CoreARC[32969:303] strong: kut
2012-11-13 05:11:47.196 CoreARC[32969:303] person dealloced
2012-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了?我又要求证一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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);
}
|
输出结果:
1
2
3
4
5
6
7
8
9
10
| 2012-11-13 05:19:13.374 CoreARC[32994:303] strong: kut
2012-11-13 05:19:13.376 CoreARC[32994:303] weak: kut
2012-11-13 05:19:13.377 CoreARC[32994:303] inner autorelease pool exiting....
2012-11-13 05:19:13.377 CoreARC[32994:303] person dealloced
2012-11-13 05:19:13.378 CoreARC[32994:303] inner autorelease pool exited
2012-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块,及时清理内存。
局部引用在函数内部的生命周期又是怎么样的?
还是上代码:
1
2
3
4
5
6
7
| void testScope(void)
{
BWPerson *person = [[BWPerson alloc] initWithName:@"kut"];
NSLog(@"%@", person);
NSLog(@"function exiting.......");
}
|
输出是:
1
2
3
4
5
6
| 2012-11-13 05:32:50.713 CoreARC[33034:303] kut
2012-11-13 05:32:50.715 CoreARC[33034:303] function exiting.......
2012-11-13 05:32:50.715 CoreARC[33034:303] person dealloced
2012-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,那么这个对象的生命周期又是怎么样的呢?还是要求证一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| 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;
}
|
输出是:
1
2
3
4
5
6
7
| 2012-11-13 05:38:47.149 CoreARC[33053:303] kut
2012-11-13 05:38:47.151 CoreARC[33053:303] function exiting.......
2012-11-13 05:38:47.151 CoreARC[33053:303] after testScope: kut
2012-11-13 05:38:47.151 CoreARC[33053:303] autorelease pool exiting.
2012-11-13 05:38:47.152 CoreARC[33053:303] person dealloced
2012-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初始化,而应该用使用字段,就像如下那样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| //
// 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一次呢?
我再写一些代码做一次试验:
1
2
3
4
5
6
7
8
9
10
| 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.........");
}
|
输出是:
1
2
3
4
5
6
7
| 2012-11-13 06:10:34.433 CoreARC[33133:303] team: NoJobTeam
2012-11-13 06:10:34.434 CoreARC[33133:303] function exiting.........
2012-11-13 06:10:34.435 CoreARC[33133:303] Team dealloced
2012-11-13 06:10:34.435 CoreARC[33133:303] person dealloced
2012-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方法或者生成方法返回对象,而用蛋疼的指针呢? 比如:
1
2
3
4
5
| void generatePerson(BWPerson **p2p);
BWPerson *person = nil;
generatePerson(person);
NSLog(@"%@", person);
|
很明显这语法从逻辑上意思是,我给一个id的地址给你,你把这个地址写上id值就可以了。可是,很明显,p2p所指的内存只是个函数局部变量(在定义p2p时只是定义了p2p是strong的,却完全没有定义它指向的内存是什么属性的,默认是assign,所以……),一过函数范围就立马被释放。唯一不被释放的方法就是,让p2p所指的那块内存不是局部的,而是由上层的autoreleasepool处理的。这时__autoreleasing就派得上用场了。
其实上面的代码经过LLVM之后,得到的代码是这样的:
1
2
3
4
5
6
7
| 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.另一种(这种蛋疼的方式很有问题):
1
2
| 1)先临时把主对象赋给一个weak对象,
2)blocks操纵的是weak对象,不过要先检测这个weak对象是不是nil,当然,一般都不会为nil,因为在方法里,如果一旦有weak引用到strong对象的话,这个strong对象会被改为__autoreleasing对象(貌似会变成nil的可能性相当大,因为block都是异步执行,可能执行的时候方法已经退出,这时如果外层是一个@autoreleasepool块的话,weak对象就为nil了)。
|
关于modal controller的生命周期
一般情况下我们会在action里做这样的事情:
1
2
3
| 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++
1
2
3
4
5
| void *p = ...;
id obj = (__bridge id)p;
// 等同于:
id obj = (id)p;
|
retain: __bridge_retained:就是retain赋值,retain count++
1
2
3
4
5
6
| void *p = ...;
id obj = (__bridge_retained id)p;
// 等同于:
id obj = (id)p;
[obj retain];
|
retain and release: __bridge_transfer:就是先retain赋值,然后release自己。
1
2
3
4
5
6
7
| 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,那么,这个赋值过程就是:
1
2
3
| [value retain];
[_contents release];
_contents = value;
|
要知道,property不是变量,它是方法,所以,不能按变量的方式来理解,那么LLVM在这样的代码里为我们做了什么呢?这就不得而知,只是知道,把上面的代码改成__bridge_transfer
的话,程序会崩溃,如果改成__bridge_retained
的话,则编译通不过。
所以,这个方面的事情先放一下,以后有机会或者时间,再详细了解一下了。
总结:
终于看完,虽然最后一部份并不是完全理解,但是在自己的脑子里,ARC已经可以在大部份代码熟练应用了。
完成这篇日志,收工。