ARC 与 MRC总结
以前在学校写项目都是用的ARC,来到公司发现都是用MRC写代码。发现自己内存管理这一块比较薄弱,这里总结一下。
相关文章链接:
Properties相关介绍
Memory Management
iPhone开发之深入浅出(1)-ARC是什么
在Objective-c里面使用property教程
iPhone开发之深入浅出 (3) — ARC之前世今生
Objective-C 内存管理之引用计数
对象生成的时候必定被某个持有者拿着,如果有多个持有者的话,其引用计数就会递增;相反失去一个持有者那么引用计数即会递减,直到失去所有的持有者,才真正地从内测中释放自己。
关于引用计数的引用,Objective-C高级编程iOS和OS X多线程和内存管理 这本书籍中有个很好的例子。 以开关房间的灯为例子介绍引用计数机制。
规则:
- 自己生成的对象,自己所持有, 用alloc/new/copy/mutablecopy 名称开头的方法创建的对象。—>生成对象并持有所有权。
- 非自己生成的对象,自己也能持有。用retain方法持有对象。一个对象可以被多个人持有 –>获取对象的所有权
用alloc/new/copy/mutablecopy
名称以外开头的方法创建的对象,属于非自己生成并持有的对象。比如说id obj = [NSMutableArray array]
。这句代码只是取的了一个非自己生成并持有的对象, 但是变量 obj目前并不持有这个生成的对象,那么如何持有这个对象呢,使用retain
方法。
通过alloc/new/copy/mutablecopy
名称开头的方法创建的对象表示自己生成的对象自己所持有,那么具体代码实现应该是怎么样的呢: 原封不动的返回用alloc生成并持有的对象,就能让这个方法的调用方生成并持有该创建的对象。注意这里方法的名字是以alloc
为前缀的。
- (id)allocObject
{
id obj = [[NSObject alloc]init];
return obj;
}
如果通过某种方法获得的对象存在但是不持有该对象,方法该如何实现呢。根据命名规则,要alloc/new/copy/mutablecopy
之外的命名方法,因此:
- (id)Object
{
id obj = [[NSObject alloc]init];
[obj autorelease];
return obj;
}
通过这个方法获得的对象表示暂时不持有该对象,可以在通过retain
方法来持有该对象。
- 不再想持有某对象时,必须释放所有权,release方法 –>释放对象的所有权。
- 不能释放已经不再持有所有权的对象。。 –>释放对象资源。
通过alloc/new/copy/mutablecopy
为前缀的方法或者retain
方法获得的对象表示自己持有该对象,在不需要该对象的时候需要将其释放。
MRC如何使用
ARC出现之前,都是使用的MRC。Cocoa框架中的Foundation框架类库的NSObject类担负内存管理的职责。这些方法都是NSObject中的实例方法。
MRC的缺点:
MRC 手动管理内存会需要时时刻刻注意内存是否有泄漏, 手动分配内存的代码维护起来也比较麻烦,而且写起来也比较复杂。
比如说一段代码中,有一个switch-case 语句,每个case中都要return返回,那么如果代码用MRC来写的话,需要在每个case的return语句之前把你方法中alloc的对象都要逐一release掉。这样写起来非常麻烦,而且哪天代码发生变更,return的位置发生了变化,需要release的对象肯定也不一样。可见MRC写代码是很繁琐的。
在MRC情况下:
- 如果一个方法以init或者copy开头,那么返回给你的对象的引用计数是1,并且这不是一个autorelease的对象。换句话说,你调用这些方法的话,你就对返回的对象负责,你再用完之后必须手动调用release来释放内存。
- 如果一个方法不是以init或者copy开头的话,那么返回的对象引用计数为1,但是,这是一个autorelease对象。换句话说,你现在可以放心使用此对象,用完之后它会自动释放内存。但是,如果你想在其它地方使用它(比如换个函数),那么,这时,你就需要手动retain它了。(记得用完release)。
MRC的关键就是平衡好alloc/retain/copy
和 release/autorelease
。否则会出现悬浮指针或者内存泄露的问题。
MRC编程准则:
- 生成对象时,使用autorelease、
如果不使用autorelease
,生成之后,在return之前忘记release
的话就会造成内存泄露。 - 对象带入的时候,先
autorelease
后在retain
对象代入的时候,如果之前不将变量所持有的对象释放,那么很可能引起内存泄露。比如下面的代码:
- (id)Object
{
_member = [[TempValue alloc] init];
}
- (void)setValue:(TempValue *)value {
_member = value;
// 这时,之前持有的对象因为没有 release 而引起内存泄露
// 当然,先 [_member release] 后再代入也是可以的,但是当与「对象在函数中返回时」的问题一同考虑时,如果没有 return [[object retain] autorelease] 的保证,这里即使 [_member release]也是百搭
// 详细的解释见下
}
除 alloc/new/copy/mutableCopy
开头函数以外的函数中,有对象返回时,使用return 【[object retain]autorelease] 这里为什么要这么做。看下面的代码例子:
@implementation FooClass
- (void)setObject:(MyObject *)object;
{
// 这里故意没有使用 autorelease,以便说明问题
[_object release];
_object = [object retain];
}
- (id)object;
{
return _object;
}
- (void)dealloc;
{
[_object release];
[super dealloc];
}
@end
@implementation BarClass
- (void)doStuff;
{
FooClass * foo = [[FooClass alloc] init];
// 创建第一个对象,引用计数 = 1
MyObject * firstObject = [[MyObject alloc] init];
// setObject中由于 [object retain] ,引用计数 = 2
[foo setObject:firstObject];
// 释放一次,引用计数 = 1;这之后对象有正确的所有权属性
[firstObject release];
// 通过非 alloc/new/copy/mutableCopy 开头函数得到对象
// anObject 指向第一个对象,但是并没有其所有权,对象引用计数 = 1
MyObject * anObject = [foo object];
[anObject testMethod];
// 创建第二个对象
MyObject * secondObject = [[MyObject alloc] init];
// setObject中由于 [_object release]; 第一个对象引用计数 = 0,内存被释放
[foo setObject:secondObject];
[secondObject release];
// 程序在这里崩溃了,因为 anObject 指向了一个空地址
[anObject testMethod];
}
@end
解决问题的几种可行方案:
//第一种方案。让生成的对象变成autorelease,这样可以解决上面的问题。将生成的对象放入到最近的自动释放池中,对象的生命周期被延续。
@implementation BarClass
- (void)doStuff;
{
FooClass * foo = [[FooClass alloc] init];
MyObject * firstObject = [[[MyObject alloc] init] autorelease];
[foo setObject:firstObject];
MyObject * anObject = [foo object];
[anObject testMethod];
MyObject * secondObject = [[[MyObject alloc] init] autorelease];
[foo setObject:secondObject];
[anObject testMethod];
}
@end
//第二种方案,对象代入时,先autorelease后再retain
- (void)setObject:(MyObject *)object;
{
[_object autorelease];
_object = [object retain];
}
- (id)object;
{
// 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则
return _object;
}
//第三种方案,对象在函数中返回时,使用return [[object retain] autorelease];
- (void)setObject:(MyObject *)object;
{
[_object release];
_object = [object retain];
}
- (id)object;
{
// 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则
return [[_object retain] autorelease];
}
这里参考文章: ARC的前世今生
总结上面上面3中方法,虽说是从不同角度入手解决了这个问题,但是基本原则不变,利用 NSAutoreleasePool 机制帮程序员维护代码,管理内存。
如果你觉得3种编码原则怎么搭配使用,在什么样的场合下选择比较麻烦,不要紧,都用就得了。我们牺牲的只是 NSAutoreleasePool 中的一些内存,一小许性能损失罢了,这总比我们的程序崩溃了强。
ARC规则和使用
简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具.比如如果不是 alloc/new/copy/mutableCopy 开头的函数,编译器会将生成的对象自动放入 autoReleasePool 中。如果是 __strong 修饰的变量,编译器会自动给其加上所有权。等等,详细,我们根据不同的关键字来看看编译器为我们具体做了什么。并从中总结出 ARC 的使用规则。
ARC的优点:
- 代码总量变少,节省劳动力。但是代码实际上生成的安装包大小并不减少。
- 写代码变得简单,不用担心烦人的内存管理。
ARC有效时,id类型必须附加上所有权的修饰符。一共有以下4中所有权修饰符:
__strong, __weak, __unsafe_unretained, __autoreleasing
__strong修饰符:
__strong修饰符是id类型和对象类型
的默认的所有权修饰符。
id obj = [[NSObject alloc]init]; = id __strong obj = [[NSObject alloc]init];
在某函数作用域下+ARC有效。
{
id obj = [[NSObject alloc]init]
}
等同于某函数作用域下+ARC无效
{
id obj = [[NSObject alloc]init];
[obj release];
}
在ARC有效的情况下,__strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时被废弃,变量被废弃,强引用也随之失效,那么引用的对象也会随之释放。
__strong修饰符表示对对象的强引用,不管是用alloc/new/copy/mutablecopy为前缀的方法来获取对象,还是用其他方法获得的对象,都会持有该对象。
//ARC情况下: 编译器会检查是否以alloc/new/copy/mutableCopy以外的方法来取得对象,如果不是自动将返回值的对象注册到autoreleasepool
id __strong array = [NSMutableArray array]; __strong修饰的array持有生成的对象。
//等于非ARC情况下的,
id array = [[NSMutableArray array]retain]
[array autorelease]
//或者对象在函数返回时,使用return [[Object retain]autorelease]所描述的。
strong修饰符和 weak, unsafe_unretained, autoreleasing修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil。
通过__strong修饰符,不必再像非ARC的情况下,键入retain或者release。这样ARC有效,这样可以通过简单的编程就遵循了Objective-c内存管理的思考方式。
__weak修饰符:
为了防止循环引用,引出__weak
修饰符。带有__weak
修饰符的变量不持有对象。在持有某个对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被复制的状态(空弱引用) 另外通过检查附有__weak
修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。
__weak
只适用于iOS5以上及OS X Lion以上。iOS4上可以使用__unsafe_unretained修饰符。
__weak 声明的变量其实是被放入一个weak表中,该表和引用计数的表格类似,是一个Hash表,都是以对象的内存地址做key,同时,针对一个对象地址的key,可以同时对应多个变量的地址。
__unsafe_unretained修饰符:
正如名字所表示,unsafe,这是一个不安全的所有权修饰符。ARC模式下的内存管理是编译器的工作,但是附有__unsafe_unretained修饰符
的变量不属于编译器的内存管理对象。
id __unsafe_unretained obj = [[NSObject alloc]init];
将自己生成并持有的对象赋值给附有__unsafe_unretained
修饰符的变量中,这里和__weak一样,因为自己生成并持有的对象不能继续为自己所有,生成的对象立马被释放。
id __unsafe_retained obj1 = nil
{
id __strong obj0 = [[NSObject alloc]init]; //obj0自己生成并持有NSobject生成的对象。
obj1 = obj0; // 虽然obj1被赋值,但是由于是__unsafe_retained,所以即不持有该对象的强引用,也不持有该对象的弱引用。这里就会出现和__weak的区别。在超出变量作用域的时候obj1就不会自动设置为nil。
}
和weak的区别: weak修饰的在超出变量作用域就会自动设置为nil,unsafe_retained修饰的则不会。所以在使用unsafe_retained修饰符时,赋值给附有__strong修饰符的变量时,要确保被赋值的对象却是存在。
__autoreleasing 修饰符:
ARC有效的时候不能直接使用autorelease方法。 也不能使用NSAutoreleasePool类。
但是可以这样使用:
//ARC 的写法。
@autoreleasepool{
id __autoreleaseing obj = [[NSObject alloc]init];
//相当于在MRC的情况下这样写。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease]
[pool drain];
ARC有效时需要遵守的规则
1.不使用 retain/release/retainCount/autorelease .
内存管理是编译器的工作,所以不需要额外调用内存管理的这些方法。
设置了ARC有效的话,使用这些方法就会编译出错。
2.不显式调用dealloc
@property 和 @synthesize
一个对象中的属性(propertys
)可以让其他对象查看或者更改其状态,但是在一个良好的面向对象的系统中,不可能直接来访问一个对象的各种内部属性(propertys
)。所以通过getter
和setter
方法来作为同其他对象内部属性的交互的一个抽象。 如下图所示:
而@property
的作用就是可以让编译器帮助我们自动生成getter和setter
方法,而不用我们每次都手动的去创建。 但是它不仅仅只有这一个作用,还包含内存管理的一些东西。 通过设置一些 attributes
,来改变setter 和getter方法中的一些行为。
@property 不仅仅默认自动生成getter和setter
方法,还默认生成了一个实例变量。比如属性为running,则默认生成了一个_running实例变量。
1. getter== && setter ==
如果不喜欢@property‘s 的默认getter和setter
方法的命名规则,可以通过使用getter== && setter ==
attribute来改变名字。
1 | @property(getter = getRuning) Bool running |
这个时候通过(.+属性)还是可以正确的调用getter和setter方法,但是此时[对象 runing]这个方法就已经不存在了,被我们新生成的getRunning方法覆盖了。
2. readonly
这个比较简单,就是让属性只读。不可以让其他对象修改设置了readonly
的属性。但是还是可以通过设置其他方法来修改这个属性,只不过不能通过默认的那个setter方法了。
nonatomic/automic //原子性
原子/非原子
nonatimic:禁止多线程,变量保护,提高性能
automic:操作是原子行为。
(assign/retain/copy)改变setter的行为,在MRC下使用的
assign: 默认类型,setter方法直接赋值,而不进行retain操作.
retain: setter方法对参数进行release旧值,再retain新值。(注意这个attribute不可以在 ARC下使用,ARC下对应的是strong)
copy: setter方法进行Copy操作,与retain一样
strong/ weak/copy 在ARC下使用的
- strong就是在MRC下的retain,只不过在ARC下都是用strong来写的。表示持有对象,并且引用计数加1.ARC下使用strong,MRC下使用retain
- weak 就相当于MRC下的assgin,直接赋值,不持有对象,引用计数不变。ARC下使用weak,MRC下使用assign。
- copy。类似strong/retain,必须是要赋值的对象遵循
NSCopying protocol
,
在MRC情况下,推荐做法NSString用copy,delegate用assign(这里一定要用assign,ARC下用weak),非objc数据类型的如一些基础数据类型(int,float)用assign(ARC下也是assign)。其他的objc类型,NSArray,NSDate用retain(ARC下就是strong)
循环引用
主要常见的有Delegate模式,一般delegate都是设置为weak的。用weak@property来声明delegate变量。