第一部分 自动引用计数
1.1 什么是自动引用计数
Auto Reference Counting (ARC)
1.2 内存管理 / 引用计数
现实问题类比 - 办公室开灯关灯
内存管理的思考方式
OC中管理内存的方法不包含在语言中,而是包含在cocoa框架中,由NSObject类来负担内存管理的职责。
(1)最早进入办公室的人开灯。
(2)之后进入的人,需要照明。
(3)下班离开的人不需要照明。
(4)最后离开办公室的人关灯(此时此人无人需要照明)。
照明设备操作 | OC 对象动作 |
---|---|
开灯 | 生成对象 |
需要照明 | 持有对象 |
不需要照明 | 释放对象 |
关灯 | 废弃对象 |
对象操作 | OC 方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutablecopy等方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc |
内存管理的思考方式
- 自己生成的对象,自己所持有
// 自己生成并持有对象
id obj = [[NSObject alloc] init];
// 自己持有对象
- 非自己生成的对象,自己也能持有
// 取得非自己生成并且持有的对象
id obj = [NSMutableArray array];
// 取得对象存在,但是不持有对象
[obj retain];
// 自己持有对象
- 不再需要自己所持有的对象时释放
// 自己生成并且持有对象
id obj = [[NSObject alloc] init];
// 自己持有对象
[obj release];
// 释放对象
// 对象一经释放,对象不可再被访问
- 非自己持有的对象无法释放
(1)自己生成,自己持有
// 自己生成并持有对象
id obj = [[NSObject alloc] init];
// 自己持有对象
[obj release];
// 对象已经释放
[obj release];
// 释放之后再次释放已非自己所持有的对象,应用程序崩溃
// 再度废弃已经废弃了的对象的时候崩溃,访问已经废弃的对象时崩溃
(2)取得对象存在,但自己不持有对象
id obj1 = [obj0 object];
// 取得对象存在,但自己不持有对象
[obj1 release];
// 释放了非自己持有的对象,肯定会导致应用程序的崩溃
使用某个方法生成对象
在这个方法中,我们使用了autorelease方法,使用该方法,可以取得对象的存在,但自己不持有队形,autorelease提供这样的功能,是对象在超出指定生存范围时候能够正确的释放(调用release方法)。
如果一个对象调用release方法,该对象在内存中立即被释放
如果一个对象调用autorelease方法,不立即释放,而是注册到autoreleasepool中,pool结束的时候会自动调用release。
- (id)allocObj{
id obj = [[NSObject alloc] init];
// 自己持有对象
[obj autorelease];
// 取得对象存在,但自己不持有对象
return obj;
}
alloc/retain/release/dealloc的实现(GNUSetup(源代码参考)/Cocoa(追溯))
GNUSetup框架是开源框架,也是Cocoa框架的可互换框架
- GNUSetup的实现方法是把引用计数放在该类的存储位置的头部,去记录引用信息;(好处:少量代码即可完成 / 能够统一管理引用计数内存块和对象用内存块)
- Cocoa则是采用散列表(引用计数表)来管理引用计数。(对象用内存块的分配无需考虑内存快头部 / 引用计数表存有内存块地址,可从各个记录追溯到各对象的内存块)
autorelease
类似于C语言中局部变量的特性(在C语言中,程序执行时,若某自动变量超出其作用域,该变量将被自动废弃)。
autorealease会像C语言那样对待对象实例,当超出其作用域(相当于变量作用域)时,对象实例的release实力方法被调用,另外和C语言不同的是,编程人员可以设定变量的作用域。
源代码表示如下
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
NSRunloop每次循环的过程中NSAutorealeasePool对象被生成或废弃。
尽管如此,但在大量产生autorelease的对象时,只要是不放弃autoreleasePool对象,那么生成的autoreleasepool对象就不能被释放,因此会产生内存不足的现象。一个典型的例子,图像文件独如刀NSData对象,并从中生成UII看嘛个度夏款能够,改变该对象尺寸后生成新的UIImage对象。这种情况下,就会产生搭理那个的autorelease对象。
for (int i = 0; i < 图像数; i++) {
// 读入图像
// 大量产生autorelease对象
// 由于没有废弃autoreleasePool对象最终导致内存不足
}
在这种情况下,有必要在适当的地方生成持有或者废弃
for (int i = 0; i < 图像数; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 读入图像, 大量产生autorelease对象
[pool drain];
// 通过[pool drain]
// autorelease的对象被一起release
}
更直观一点,我们现在用用字符串拼接,并且观察内存变化,代码如下:
memory会持续增长,可想而知,如果我们将来NSString替换成占用内存更大的UIImage,会导致内存的增长更快和更大,严重的时候还会导致App崩溃。
int largeNum = 999 * 9999;
for (int i = 0; i < largeNum; i++) {
NSString *str = @"Hellow World";
str = [str stringByAppendingFormat:@" - %d", i];
str = [str uppercaseString];
NSLog(@"%@", str);
}
修改如下
for (int i = 0; i < largeNum; i++) {
@autoreleasepool {
NSString *str = @"Hellow World";
str = [str stringByAppendingFormat:@" - %d", i];
str = [str uppercaseString];
NSLog(@"%@", str);
}
}
autoreleasePool实现原理
GNUSetup和Cocoa框架实现的大体思路总结
如果调用NSObject类的autorelease方法,该对象就会被追加到正在使用的NSAutoReleasePool对象中的数组里。这个数组维护处在autoreleasepool的所有类对象,当一个对象进入autoreleasepool的时候就像数组添加该类,如果是多层autoreleasepool嵌套,理所当然的会使用最内侧的对象。代码如下:
- (void)drain
{
[self dealloc];
}
- (void)dealloc
{
[self emptyPool];
[array release];
}
- (void)emptyPool{
for (id obj in array) {
[obj release];
}
}
1.3 ARC规则
概要
ARC / MRC可以混编
内存管理的思考
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 不再需要自己所持有的对象时释放
- 非自己持有的对象无法释放
以上这些规则同样适用,只是源代码的记述方法上稍微有些不同。
所有权修饰符
- __ strong修饰符
- __weak修饰符
- __unsafe_unretain修饰符
- __autoreleasing修饰符
__ strong修饰符
__ strong修饰符是id类型和对象类型默认的修饰符。以下两句代码等价。
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
__ strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用对象会被释放掉,以下两句代码等价(注意区分MRC / ARC)。
{
id __strong obj = [[NSObject alloc] init];
}
// 在ARC无效时
{
id obj = [[NSObject alloc] init];
[obj release];
}
附有_strong修饰符之间的变量可以相互赋值,我们用代码来掩饰这一过程
{
id __strong obj0 = [[NSObject alloc] init]; /* 对象A */
// obj0对对象A强引用
id __strong obj1 = [[NSObject alloc] init]; /* 对象B */
// obj1对对象B强引用
id __strong obj2 = nil;
// obj2不持有任何对象
obj0 = obj1;
/*
obj0 持有由obj1赋值对象B的抢引用
因为obj0被赋值,所以原先持有的对象A的强引用失效。对象A的所有者不存在,因此废弃对象A
此时,对象B的持有者变成了 obj0和obj1
*/
obj2 = obj0;
/*
此时,对象B的持有者变成了 obj0 obj1 obj2
*/
obj1 = nil;
/*
因为nil被赋予到obj1,所以对x对象B的强引用失效
此时,对象B的持有者变成了 obj0 obj2
*/
obj0 = nil;
/*
因为nil被赋予到obj0,所以对x对象B的强引用失效
此时,对象B的持有者变成了 obj2
*/
obj2 = nil;
/*
因为nil被赋予到obj0,所以对x对象B的强引用失效
对象B的所有者不存在,因此废弃对象B
*/
}
__strong修饰符和后面要讲的__weak和__autoreleasing修饰符一起,可以保证这些附有修饰符的自动变量初始化为nil
下面两端代码等价
id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;
__ weak修饰符
到此位置,strong似乎可以完美的管理内存,但是还有一个重大的问题就是循环引用
以下为循环引用示例代码
类成员变量相互引用
{
id test0 = [[Test alloc] init]; /* 对象A */
/*
test0持有Test对象A的强引用
*/
id test1 = [[Test alloc] init]; /* 对象B */
/*
test1持有Test对象B的强引用
*/
[test0 setObject: test1];
/*
Test对象A的obj_成员变量持有Test对象B的强引用
此时拥有Test对象B的强引用的变量为 Test对象A的obj_和test1
*/
[test1 setObject: test0];
/*
Test对象B的obj_成员变量持有Test对象B的强引用
此时拥有Test对象A的强引用的变量为 Test对象A的obj_和test0
*/
}
/*
因为Test0变量超出其作用域,强引用失效
所以自动释放Test对象A
因为Test1变量超出其作用域,强引用失效
所以自动释放Test对象B
此时
持有A的强引用变量为B的ocbj_
持有B的强引用变量为A的ocbj_
发生内存泄漏!
*/
对自身的循环引用
id test0 = [[Test alloc] init]; /* 对象A */
[test0 setObject:test0];
__weak修饰符提供弱引用。弱引用不能持有对象实例。
错误写法,这么写,因为生成的实例没有被引用所以生成之后会立即被销毁
id __weak obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
id __weak obj1 = obj;
{
// 自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
// 因为obj0的对象为强引用,所以自己持有对象
id __weak obj1 = obj0;
// obj1变量持有生成对象的弱引用
}
/*
因为obj0变量超出其作用域,强引用失效
所以自动释放自己持有的对象
因为对象的所有者不存在,所以废弃该对象
*/
这样的话,两个类互相弱引用也不会有问题。
此外__weak还有一个优点,在持有某对象的弱引用时,若该对象被废弃,则弱引用将自动失效且处于nil的赋值状态。
__ unsafe_unretained修饰符
unsafe_unretained修饰符正如其名unsafe,是不安全的修饰符,尽管ARC的内存管理是编译器的工作,但附有__ unsafe_unretained修饰符的变量不属于编译器内存管理对象。
/* ARC无效 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
可以非显示的使用__autoreleasing修饰。
以下两句等价
id obj;
id __strong obj1;
以下两句等价
id *obj;
id __autoreleasing *obj1;
以下两句等价
NSobject **obj;
NSobject *__autoreleasing *obj;
网友评论