iOS与OS X多线程和内存管理 - 自动引用计数
前言
iOS与OS X多线程和内存管理 读书笔记(一) 自动引用计数
笔记##
内存管理/引用计数
使用办公室的照明灯来做比喻,对象相当于照明灯,但是会有许多人需要这个照明灯,也就是持有对象。只要有人持有这个对象,就不能释放对象;若没人在需要对象,就释放对象。
例如:a = [class new]; b = a; 那么a生成对象时持有对象,b也持有对象。只有当a和b都不持有对象时,才能释放对象。
对于对象,有4个关键词。生成,持有,释放和废弃。
看看对应的操作:(以下的方法均是NSObject类的方法)
- 使用
alloc/new/copy/mutableCopy
开头的方法生成并持有对象 retain
方法持有对象release
方法释放对象dealloc
方法废弃对象
- 使用
自己生成的对象自己持有
1
2
3id obj = [[NSObject alloc] new]; // alloc方法生成并持有对象
// or
id obj = [NSObject new]; // new 和 alloc + init 一样另外
copy
和mutableCopy
也是生成对象的副本,并持有。非自己生成的对象,自己也能持有
1
2id obj = [NSMutableArray array]; // 对象存在了,但是obj对象不持有对象
[obj retain]; // 现在obj已经持有对象了不在需要自己持有的对象时释放
1
2id obj = [NSObject new]; // obj持有对象
[obj release]; // obj不再需要对象时,释放对对象。 对象释放后就不能被访问到了。- 不持有可以这样理解:可以使用对象,但是没有强行占用对象。
- 无法释放非自己持有的对象
1
2id obj = [NSMutableArray array]; // obj自己不持有对象
[obj release]; // 程序崩溃,因为不能释放不是自己持有的对象来看看如何实现alloc/retain/release/dealloc方法 (注意:这部分对于理解最为关键)。
alloc的实现
1
2
3
4
5
6
7
8
9struct obj_layout {
NSUInteger retained;
}
+ (id)alloc {
int size = sizeof(struct obj_layout) + 对象大小;
struct obj_layout *p = (struct obj_layout *)calloc(1, size); // 分配一个长度为size的连续内存空间 p为该内存空间的第一个地址。这个内存空间的内存计量单位为一个obj_layout struct
return (id)(p + 1); // 该内存空间的第一个内存单位放的是obj_layout这个结构体,之后是对象的内存空间,已经被置为0。返回的是指向对象内存空间的首地址的指针。
}看看retainCount的实现,这个方法用来查看对象的引用计数
1
2
3
4- (NSUInteger)retainCount {
return ((struct obj_layout *) self)[-1].retained + 1;
}
// 因为self指向的是对象内存的首地址。将这个地址看成obj_layout,也就是地址计数以obj_layout结构体的内存大小为单位。-1就指向了对象上方存储obj_layout结构体的首地址。这样就可以取出retained。因为retained默认是0,所以需要加1。根据2,可以方便推出retain方法是将retained值加一;而release方法是将retained值减一。注意当release时发现retained值等于0,那么就不执行减一操作了,直接执行dealloc方法,因为已经没有人只有该对象了。
dealloc的实现
1
2
3
4- (void)dealloc {
struct obj_layout *o = &((struct obj_layout *) self)[-1];
free(o); // 释放之前由calloc分配的内存空间
}
autorelease:
当变量调用对象的autorelease方法时,说明他已经不持有这个对象了。但是这个对象并没有被释 放,还是可以被取到。
autorelease的使用方法:
- 生成并持有NSAutoreleasePool对象
- 调用已分配对象的autorelease方法
- 废弃NSAutoreleasePool对象
NSAutoreleasePool对象生成和废弃的这个期间,相当于对象的作用域。当废弃NSAutoreleasePool对象时,自动调用对象的release方法。来看看代码:
1
2
3
4NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; // 这个时候才会release对象注意:使用autorelease时,太多的需要autorelease的对象可能由于没有及时的release,会导致内存不足。PS:其实还是在dealloc的时候释放内存,但是因为在release时,若没有变量引用对象的话,会自动调用对象的dealloc方法,所以release也可以说是释放内存吧。
看看下面代码:
1
2id array = [NSMutableArray arrayWithCapacity:1]; // array不持有对象
id array = [[NSMutableArray alloc] initWithCapacity:1] autorelease]; // array也不持有对象上面两行代码是等价的。
autorelease的实现
1 | - (id)autorelease { |
- 注意不能autorelease NSAutoreleasePool对象
ARC规则
引用计数式内存管理的本质并没有改变,只是ARC自动帮我们处理“引用计数”的相关部分。
编译单位可以选择ARC是否有效。一个应用程序可以以混合ARC和非ARC的方式进行编译。
*NSObject 是指向NSObject类的指针。id*相当于C语言中的*void ***。
在ARC下,类型必须附上所有权修饰符,有一下几种类型
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
__strong修饰符:是id类型和所有对象类型默认的所有权修饰符。
1
2
3id obj = [[NSObject alloc] init];
// 上面实际的代码如下
id __strong obj = [[NSObject alloc] init];再来看看一下代码:
1
2
3
4
5
6
7
8
9
10/* ARC下 */
{
id __strong obj = [[NSObject alloc] init];
} // 出了作用域,obj对象自动被释放
/* 非ARC下的等价代码 */
{
id obj = [[NSObject alloc] init];
[obj release]; // 出作用域之前要记得释放obj对象,不然会导致内存泄漏
}__strong修饰符表示对对象的强引用。在超出作用域时,强引用变量被废弃(obj指针被废弃),与此同时它引用的对象也会被释放(对象被释放)。
看看对于非自己生成的对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14/* ARC下 */
{
id __strong obj = [NSMutableArray array]; // 这里的obj是持有对象的
// do something here
}
/* 非ARC下的等价代码 */
{
id obj = [NSMutableArray array];
[obj retain]; // obj要手动持有
// do something here
[obj release]; // 出作用域时要手动释放
}__weak修饰符主要是为了防止相互引用。__weak指的是弱引用,也就是变量不能持有对象。
1
2
3
4{
id __strong obj0 = [[NSObject alloc] init]; // obj0强引用对象,持有
id __weak obj1 = obj0; // obj1弱引用对象,不持有
}所以在出作用域时obj0废弃,不在有变量持有对象,对象被释放。可以通过检查附有__weak修饰符的变量是否为nil,来判断对象是否已经被释放。
__unsafe_unretained作用和__weak一样,但是如名字所言,它是不安全的。当这种变量指向的对象被释放时,weak型的变量就被置成nil(正确的做法),但是unsafe的变量可能还指向某个地址(不安全的,因为那个地址的对象已经被释放了)。
来看看autorelease,先看段代码:(PS:有点迷糊)
1
2
3
4
5
6
7
8
9
10/* ARC无效 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* ARC有效 */
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}但是通常不需要再@autorelease里面显式的使用__autoreleasing修饰符。
在ARC有效时有以下规定:
- 不能使用retain/release/retainCount/autorelease方法
- 不能使用NSAllocateObject/NSDeallocateObject
- 要遵循方法的命名规则。alloc/new/copy/mutableCopy开头的方法,要返回自己生成并持有的对象。init开头的方法,基本只是对alloc方法返回的对象进行初始化并返回该对象。
- 不要显式调用dealloc方法。因为在对象需要废弃时,会自动调用该方法。在ARC无效时,要在dealloc方法里面调用
[super dealloc];
。但是在ARC有效时,不需要这样调用,ARC会自动帮你处理。 - 使用@autoreleasepool而不是NSAutoreleasePool
- 结构体中的成员变量不能是Objective-C对象
- id 和 void* 不懂,什么东西?这里还和__bridge有点关系
在ARC有效时,变量有各种修饰符。当然属性也会有各种修饰符。
assign == __unsafe_unretained
copy == __strong (但是赋值的是被复制的对象)
retain == __strong
strong == __strong
unsafe_unretained == __unsafe_unretained
weak == __weak
数组1.3.6 不懂
ARC的实现
ARC由下面两者来实现
- clang(LLVM编译器)3.0以上
- objc4 Objective-C运行时库 493.9以上 (主要是 runtime/objc-arr.mm)
__strong的实现
1
2
3
4
5
6
7
8
9
10{
id __strong obj = [[NSObject alloc] init];
// do something
}
/* 经过编译之后 */
id obj = objc_msgSend(NSObject, @selector(alloc)); // 执行alloc方法,obj变量持有对象,也就是强引用
objc_msgSend(obj, @selector(init)); // 执行init方法,进行一些初始化操作,无关ARC
// do something
objc_release(obj); // 在变量出作用域时,编译器自动释放对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22{
id __strong obj = [NSMutableArray array]; // obj是持有对象的
// do something
}
/* 经过编译之后 */
id obj objc_msgSend(NSMutableArray, @selector(array)); // 执行array方法,此时对象“照理来说”是已经被注册到NSAutoreleasePool里面,obj变量此时是不持有对象的
objc_retainAutoreleasedReturnValue(obj); // 编译器帮obj持有注册到NSAutoreleasePool中的变量
// do something
objc_release(obj); // 变量要出作用域了,编译器自动释放对象
/* array函数实质 */
+ (id)array {
return [[NSMutableArray alloc] init];
}
/* 经过编译之后 */
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); // 生成对象,obj持有
objc_msgSend(obj, @selector(init)); // 初始化,无关ARC
return objc_autoreleaseReturnValue(obj); // 讲道理,这一步是要将对象注册到NSAutoreleasePool中,obj不再持有对象(可以说是obj将对象持有权转交给了NSAutoreleasePool)
}按上面的说法来看,最终持有array对象的有2个:一个是NSAutoreleasePool,一个是obj。但是其实是做了优化的。
objc_autoreleaseReturnValue(obj);
这个函数观察到紧接着它自己调用的函数如果是objc_retainAutoreleasedReturnValue(obj);
的话,就不会将对象注册到NSAutoreleasePool,直接将对象给obj让它持有就行了。这个优化就是因为__strong修饰符要持有对象,与其先将对象注册到NSAutoreleasePool,再从NSAutoreleasePool中拿到这个对象给obj持有一遍;还不如直接跳过注册,直接让obj持有对象。说到底,使用NSAutoreleasePool的原因就是怕没有变量持有对象,那么就取不到这个对象了,所以让NSAutoreleasePool暂时持有对象。PS:让NSAutoreleasePool暂时持有对象这个说法不知道靠不靠谱
todo add a picture
__weak的实现
1
2
3
4
5
6
7
8
9
10
11{
id __weak obj1 = obj; // 这里obj是强引用
// do something
}
/* 编译之后 */
id = obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
// do something
objc_storeWeak(&obj1, 0);objc_storeWeak(&obj1, obj);
函数将obj指向对象的地址作为key,将obj1变量的地址作为value,注册到weak表中,并将obj1指向对象。因为一个对象可以赋给多个值,所以一个key可以对应多个值。如果第二个参数是0的话,就将obj1这个变量的地址从weak表中删除(删除value为obj1地址的那条记录,废弃obj1对象)。PS:这么说的话,其实将obj1变量的地址当成key,更方便理解。PS:我们来看看另外一种情况,就是释放对象时,但是weak变量还存在。这个时候会将weak变量置为nil。这是如何实现的呢?
- 先将weak表中key为这个对象的地址的记录找出来
- 将该地址对应的所有的weak变量找出来,将他们置为nil
- 删除这条key为对象地址的记录
由上面说明可以知道,大量使用weak变量也会消耗相应的CPU资源,所以只在需要避免循环引用时使用。
看看weak和unsafe_unretained的区别
1
2
3
4
5
6
7
8
9
10
11
12{
id __weak obj = [[NSObject alloc] init];
// do something
}
/* 编译之后 */
id = obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp); // 在weak表中插入对象,将obj指向对象
objc_release(tmp); // 释放对象后,不在有变量持有对象,这时候会将obj置为nil。安全
// do something
objc_destroyWeak(&obj); // 删除记录(上一步已经做了,这里就不做了),废弃obj变量1
2
3
4
5
6
7
8
9
10{
id __unsafe_unretained obj = [[NSObject alloc] init];
// do something
}
/* 编译之后 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj); // 因为没有变量持有对象,所以释放对象,但是obj变量还指向了原对象。危险
// do something还有就是要注意使用__weak变量时
1
2
3
4
5NSLog(@"%@", obj); // 假设这里的obj是weak变量
/* 编译之后 */
id tmp = objc_loadWeakRetained(&obj);
objc_autorelease(tmp); // 这两句说的是将obj弱引用的对象交给NSAutoreleasePool持有,以保证这个对象可以被取到。
NSLog(@"%@", tmp); // 做正事上面说的这个是,当使用weak变量的时候,会先将weak变量指向的对象交给NSAutoreleasePool持有,以保证这个对象万一被强引用它的变量释放的时候,还是可以取到。但是当多次使用weak变量的话,最好先将weak变量的对象给一个变量,让它强引用,这样就不用多次去和NSAutoreleasePool打交道,耗时间了。
1
2id tmp = obj; // 让tmp强引用对象,这里会发生一次和NSAutoreleasePool的交互,因为使用到了obj这个weak变量。这一步过后tmp和NSAutoreleasePool都持有对象
// 使用tmp做正事,而不是频繁使用obj__autoreleasing的实现
1
2
3
4
5
6
7
8
9
10@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
/* 编译之后 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj); // 交给NSAutoreleasePool持有
objc_autoreleasePoolPop(pool); // 废除NSAutoreleasePool,释放对象看看对于非自己生成并持有的对象
1
2
3
4
5
6
7
8
9
10@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
/* 编译之后 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableAttay, @selector(array)); // 不持有
objc_retainAutoreleasedReturnValue(obj); // 又重新持有对象。。。
objc_autorelease(obj); // 又交给NSAutoreleasePool持有
objc_autoreleasePoolPop(pool); // 废除NSAutoreleasePool,释放对象uintptr_t _objc_rootRetainCount(id obj);
函数可以获得obj对象当前的引用计数,也就是有多少个变量强引用该对象。注意注册到NSAutoreleasePool中的对象也算一次引用计数。也就是如同上面一直说的,可以说是NSAutoreleasePool持有对象。注意,这个函数不一定可信。。。
PS
呕心沥血啊。看得要吐了。。。