一、什么是自动引用计数
顾名思义,自动引用计数(ARC)是指内存管理中对引用采取自动计数的计数,以下摘自苹果官方的说明:
在objective-c中采用ARC机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,着在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
ARC模式需要满足以下条件:
- 使用xcode4.2或以上版本;
- 使用LLVM编译器3.0或以上版本;
- 编译器选项中设置ARC为有效;
二、内存管理/引用计数
1、概要
2、内存管理的思考方式
- 自己生成的对象,自己所持有(alloc,new,copy,mutableCopy);
- 非自己生成的对象,自己也能持有(retain);
- 不再需要自己持有的对象要释放(release,dealloc);
- 非自己持有的对象无法释放;
有关objective-c内存管理的方法,实际上不包括在该语言中,而是包含在cocoa框架中用于OS X、iOS应用开发。cocoa框架中foundation框架类库的NSObject担负内存管理的职责。objective-c内存管理中的 alloc/retain/release/dealloc 方法分别指向 NSObject 类的alloc类方法、retain类方法、release实例方法和dealloc实例方法;
/* 自己生成的对象,自己所持有; 使用一下名称开头的方法名意味着自己生成的对象只有自己持有: *alloc *new *copy *mutableCopy */ //自己生成并持有对象 id obj = [[NSObject alloc] init]; //自己持有对象
/* 非自己生成的对象,自己也能持有; */ //取得非自己生成并持有的对象 id obj = [NSMutableArray array]; //取得的对象存在,但自己不持有对象 [obj retain]; //自己持有对象
/* 不再需要自己持有的对象时释放; 自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法; */ //自己生成并持有对象 id obj = [[NSObject alloc] init]; //自己持有对象 [obj release]; //释放对象,指向对象的指针仍然被保留在变量obj中,貌似能够访问,但事实上对象一经释放绝对不能访问,访问会导致程序崩溃;
/* 无法释放非自己持有的对象; 如果在应用程序中释放了非自己持有的对象就会造成崩溃; */ //自己生成并持有对象 id obj = [[NSObject alloc] init]; //自己持有对象 [obj release]; //对象已释放 [obj release]; //释放之后再次释放已非自己持有的对象,程序崩溃
3、alloc/retain/release/dealloc实现
对象的引用计数可通过retainCount实例方法取得;
id obj = [[NSObject alloc] init]; NSLog(@"retainCount = %d", [obj retainCount]); //显示结果:retainCount = 1
在GNUsetp中的实现,具体总结如下:
- 在objective-c的对象中存在引用计数这一整数值;
- 调用alloc或是retain方法后,引用计数值加1;
- 调用release后,引用计数值减1;
- 引用计数值为0时,调用dealloc方法废弃对象;
4、苹果的实现
- +alloc
- +allocWithZone:
- class_createInstance
- calloc
alloc类方法首先调用allocWithZone:类方法,然后调用class_createInstance函数,最后通过调用calloc分配内存块;
苹果的实现大概就是采用散列表(引用计数表)来管理引用计数;
GNUstep将引用计数保存在对象占用内存块头部的变量中,苹果的实现,则是保存在引用计数表的记录中;
(GNUstep)通过内存块头部管理引用计数的好处如下:
- 少量代码即可完成;
- 能够统一管理引用计数内存块与对象用内存块;
(苹果)通过引用计数表管理计数的好处如下:
- 对象用内存块的分配无需考虑内存块头部;
- 引用计数表各记录中有内存块地址,可从各个记录追溯到各对象的内存块;
5、autorelease
{ int a; } /* 因为超出变量作用域,自动变量“int a”被废弃,不可再访问; */
autorelease会像C语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被自动调用;autorelease的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象;
- 调用已分配对象的autorelease实例方法;
- 废弃NSAutoreleasePool对象;
NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法,用源代码表示如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id obj = [[NSObject alloc] init]; [obj autorelease]; [pool drain]; //最后一行“[pool drain]”等同于"[obj release]"
在cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用NSAutoreleasePool对象来进行开发工作。
6、autorelease实现
//GNUstep/modules/core/base/Source/NSObject.m autorelease -(id)autrelease { [NSAutoreleasePool addObject:self]; }
autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法;7、苹果的实现
三、ARC规则
1、概要
同一个程序中按文件单位可以选择ARC 有效/无效;
设置ARC有效的编译方法如下:
- 使用clang(LLVM编译器)3.0或以上版本;
- 指定编译器属性为“-fobc-arc”;
xcode4.2默认设定为所有的文件ARC有效;
2、内存管理的思考方式
- 自己生成的对象,自己所持有;
- 非自己生成的对象,自己也能持有;
- 自己持有的对象不再需要时释放;
- 非自己持有的对象无法释放;
3、所有权修饰符
ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符,所有权修饰符一共有4种:
- __strong 修饰符
- __weak 修饰符
- __unsafe_unretained 修饰符
- __autoreleasing 修饰符
__strong 修饰符:
__strong修饰符是id类型和对象类型默认的所有权修饰符,在没有明确指定所有权修饰符时,默认为__strong修饰符,代码如下:
id obj = [[NSObject alloc] init]; //等价于以下代码 id __strong obj = [[NSObject alloc] init];
//上述源代码在ARC无效时的表述 id obj = [[NSObject alloc] init]; //ARC无效 { id __strong obj = [[NSObject alloc] init]; } //该源代码等价于下列代码 { id obj = [[NSObject alloc] init]; [obj release]; }
如上述代码所示,附加__strong 修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象;
__strong修饰符表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放;{ //自己生成并持有对象 id __strong obj = [[NSObject alloc] init]; //因为变量obj为强引用,所以自己持有对象 } //因为变量Obj超出其作用域,强引用失效,所以自动地释放自己持有的对象,对象的所有者不存在,因此废弃该对象
{ //取得非自己生成并持有对象 id __strong obj = [NSMutableArray array]; //因为变量obj为强引用,所以自己持有对象 } //因为变量obj超出其作用域,强引用失效,所以自动地释放自己持有的对象
“自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”只需通过对带__strong 修饰符的变量赋值便可达成。通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所需对象废弃)或对变量赋值,都可以做到“不再需要自己所持有的对象时释放”。“非自己持有的对象无法释放”,由于不必再次键入release,所以原本就不会执行。因为id类型和对象类型的所有权修饰符默认为__strong修饰符,所以不需要写上“__strong”;
__weak修饰符:
循环引用容易发生内存泄漏,所谓内存泄漏就是应该废弃的对象在超出其生存周期后继续存在;对象持有其自身时(指针指向自身),也会发生循环引用;
__weak修饰符与__strong修饰符相反,提供弱引用。强引用和弱引用广义区别:
- 强引用的存亡直接影响对象是否被释放,当修饰对象超出作用域,强引用失效,对象随之被释放;
- 弱引用除了不能决定对象是否被释放之后,其他跟强引用一样。不管对象是否被弱引用修饰,只要没有强引用指向该对象,对象就会被释放;
在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。
__unsafe_unretained 修饰符:
尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象;
__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放;
赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,程序就会崩溃;
__autoreleasing 修饰符:
ARC有效autorelease不能使用,也不能使用NSAutoreleasePool类;
//ARC有效时,源代码也能写成下面这样 @autoreleasepool{ id __autoreleasing obj = [[NSObject alloc] init]; }
在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有 __autoreleasing 修饰符的变量替代 autorelease 方法;以下总结默认基于ARC无效情况下,如果是基于ARC有效的情况下,会说明;
(1)不管这个对象是在自动释放池内还是外部创建的,当你向一个对象发送一个autorelease消息时,cocoa就会将该对象放入到最近的自动释放池中,并返回一个self;自动释放池结束销毁时(drain),统一对里面的对象调用release方法,引用计数减1;
(2)@autoreleasepool可以嵌套使用;
(3)对象执行autorelease方法后自身引用计数不会改变,而且会返回对象本身,当autoreleasepool销毁时,池中的对象引用计数才会减1;
(4)autoreleasepool存储于内存中的栈中,遵循”先进后出“原则;
//自动释放池1 @autoreleasepool{ Person *person = [[[Person alloc] init] autorelease]; //自动释放池2 @autoreleasepool{ Person *person2 = [[[Person alloc] init] autorelease]; } }
(5)如果把一个对象重复加到自动释放池如[person autorelease];[person autorelease];那么会出错。调用几次autorelease方法,在自动释放池中就释放几次,第一次自动释放对象引用计数为0,已经被销毁;第二次释放时,person变成了野指针;(6)在ARC机制中,我们用@property声明的成员变量,建议用strong修饰符;
(7)autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把对象添加到最近的autoreleasepool中,当该pool被释放时,该pool中的所有对象会被调用release方法;
4、规则
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显示调用dealloc
- 使用 @autorelease 块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结固体的成员
- 显示转换“id”和"void *"
不要显示调用dealloc:
无论ARC是否有效,只要对象的所有者都不持有对象,该对象就被废弃。对象被废弃时,不管ARC是否有效,都会调用对象的dealloc方法:
dealloc方法在大多数情况下还适用于删除已注册的代理或观察者对象;
-(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; }
在ARC无效时必须像下面这样调用[super dealloc];-(void)dealloc{ [super dealloc]; }
ARC有效时会遵循无法显示调用dealloc这一规则,如果使用就会同release等方法一样,引起编译错误;
四、ARC的实现5、属性
当ARC有效时,以下可作为这种属性声明转给你使用的属性来用:
6、数组
1、__strong修饰符
2、__weak修饰符
- 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给对象;
- 使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象;
3、__autoreleasing修饰符
4、引用计数