我的iOS内存管理-MRC-ARC
貌似每个iOS开发者都有一篇属于自己的内存管理,记录了自己对内存管理理解的深度以及广度,所以我也来记录一下我的理解。
MRC
1.与引用计数相关的alloc/copy/mutableCopy/retain/realease/deallloc
- 相关操作
对象操作 | Objective-C方法 | 引用计数 |
---|---|---|
生成并持有对象 | alloc/new/copy/mutablecopy | 1 |
持有对象 | retain方法 | +1 |
释放对象 | release | -1 |
废弃对象 | dealloc | 0 |
- 原则
-
自己生成的对象,自己持有
id object = [[NSObject alloc] init]; //以上代码通过alloc生成了一个对象,并且object持有该对象,rc = 1
另外,使用new方法也能生成并持有对象,[NSObject new]和[[NSObject alloc] init]使用效果是完全一致的。
-
自己生成的对象,自己持有
-
不是自己生成的对象,自己也能持有
通过使用表格第一行生成的对象,自己都能持有,对于alloc/new/copy/mutablecopy 以外的方法生成的对象,自己就不是持有者了,例如id obj = [NSMutableArray array];
,obj就不持有array返回的对象,仅仅取得对象的引用,但可以通过[obj retain]
让obj持有该对象。 -
谁持有,谁释放,不持有,不能释放,不再需要时,主动释放
以上两句话很好理解,谁通过表格第一行的方式生成并持有对象,或者通过retain的方式持有了对象,一旦不需要该对象了,一定要记得调用release
方法释放该对象。但只有自己不持有该对象,就一定不能调用release
。
2. MRC中的autorelease
autorelease故名思议就是自动释放,看上去很像ARC,但其实更类似于C语言中的局部变量,也就是说,超出变量作用域的时候将自动被废弃,但这里与C语言不同的是,编程人员可以手动设置其作用域。
autorelease的具体使用方法如下:
(1).生成并持有NSAutoreleasePool对象
(2).调用已分配的autorelease方法
(3).废弃NSAutoreleasePool对象
NSAutoreleasePool对象的生命周期相当于C语言变量的作用域,对于所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将对对象统一调用release方法,代码如下所示:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ; id obj = [ [NSObject alloc]init]; [obj autorelease] ; [pool drain];
另外在Cocoa框架中,如果不是使用alloc/new/copy/mutablecopy这几个方法返回的对象,其余方法返回的对象都将自动注册到NSAutoreleasePool中
id array = [NSMutableArray arrayWithCapacity:10];
其实也就等同于:
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
3.实现(以上操作对应于runtime下的实现)
- retainCount/retain/release的实现:
该理论总结自《Pro multithreading and memory management for iOS and OS X》
由于Apple对于NSObject类的源代码没有公开,此处是利用Xcode的调试器(lldb)和iOS大概追述其实现过程,在NSObject类的alloc类方法上设置断点,追踪程序的执行,所以大概得到的结论便是Apple采用散列表(引用计数表),来管理引用计数,通过传入对象的地址便可以返回对象的引用计数。
- autorelease的实现
通过查看GNUstep的源代码可以知道,当对象调用autorelease方法的时候,实际上的本质就是调用NSAutoreleasePool的addObject()方法。(所以pool当然是一个数组类型的结构喽)
-(id)autorelease{ [NSAutoreleasePool addObject:self]; }
而当pool调用drain方法的时候则是让pool中的每个对象都调用release方法。
ARC
1.ARC中的变量所有权修饰符_ _ strong,_ _ weak, _ _ unself_unretained,_ _ autoreleasing,
ARC有效时,id类型和对象类型同C语言的其他类型不同,其类型上必须加上所有权修饰符,不加时默认为_ _strong.
- _ _strong
_ strong修饰符是id类型和对象类型的默认所有权修饰符,也就是说,以下代码中默认是添加了修饰符的,并且是 _strong:
{ id obj = [[NSObject alloc]init]; }
实际上是:
{ id __strong obj = [[NSObject alloc] init]; }
如果这段代码在非ARC情况下,是这样的:
{ id obj = [[NSObject alloc] init]; [obj release] }
也就是说:作用1:赋有_ _strong 修饰符的变量在obj超出其作用域的时候,强引用自动失效,引用计数自然归0,释放其被赋予的对象(可以理解为强引用加1,对象的引用计数就加1)
还有一点,以上是将使用alloc产生的对象,在前面已经提到,这种情况下,obj是会自动持有对象的,其实,在使用alloc/new/copy/mutablecopy以外的方式生成对象的时候,如果添加描述符_ _strong ,此时obj自己就会持有对象了,与之前不同:
{ id _ _strong obj = [NSMutableArray array]; //因为_ _strong为强引用,此时obj会自动持有对象 } //obj超出作用域,强引用失效,自动释放持有对象
另外,作用2:_ strong修饰符通后面要讲的 weak修饰符, _ autoreleasing修饰符一起,可以保证附有这些修饰符的自动变量在初始化的时候为nil
id _ _strong obj0; id _ weak obj1; id _ autorelease obj2;
其实也就是:
id _ _strong obj0 = nil; id _ weak obj1 = nil; id _ autorelease obj2 = nil;
小结strong:对于之前在MRC中声明的内存管理原则中的“自己生成的对象自己持有”和“非自己生成的对象,自己也能持有”,通过 _ _strong修饰符均可实现,通过废弃 _ _strong修饰符的变量(变量作用域结束)或者对变量赋值,都可以实现,对于最后一项:“不再需要时,主动释放”,很明显,release就不需要了,作用域结束时强引用失效,默认实现了这一功能。
- _ _weak
使用_ weak的环境大伙儿都知道,是为了避免循环引用,所以理解起来较为简单,此处仅简单总结 _weak的作用即可
作用1:_ weak与 _strong修饰符相反,提供弱引用,弱引用不持有对象,也就是说弱引用不能使对象的引用计数+1.
作用2:_ weak修饰符还有另一个优点,在持有某对象的弱引用,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用),主要和后面的 _unself_unretained做对比,如下代码所示:
id _ _weak obj1 = nil; { id _ _strong obj0 = [[NSObject alloc]init]; obj1 = obj0; NSLog(@"A:%@",obj1); } NSLog(@"B:%@",obj1);
打印结果如下:
A:<NSObject>: 0x753e180 B:(null)
_ weak小结: weak修饰符只能用于iOS5以上,及OS X Lion以上版本的应用程序,在iOS4以及OS X Snow Leopard 的应用程序中,可以使用 _unsafe_unretained修饰符代替(难道是它出现的原因?)
- _unsafe_unretained
_unsafe_unretained修饰符正如其名unsafe所示,是不安全的修饰符,尽管ARC式的内存管理是编译器的工作,但附有_unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点使用时要注意。
前文提到unsafe_unretained与 _weak功能相似,但是还是有差别,下面介绍:
id _ _weak obj1 = nil; { id _ _strong obj0 = [[NSObject alloc]init]; obj1 = obj0; NSLog(@"A:%@",obj1); } NSLog(@"B:%@",obj1); 打印结果如下: A:<NSObject>: 0x753e180 B:<NSObject>: 0x753e180
解释:因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象,所以整个对象无持有着,引用计数为0,所以废弃。但很显_unsafe_unretained修饰的obj1在其指向的对象被废弃了之后,依然指向其地址,所以这就是问题(书上说这叫悬垂指针,估计就是咱们理解的野指针吧。)
unsafe_unretained小结:在iOS4以及OS X Snow Leopard 的应用程序中,务须使用 unsafe_unretained修饰符代替 _weak,所以在使用_unsafe_unretained的时候,虽然其指向地址有值,但一定要确保其是否真实存在
- _ _autoreleasing
ARC有效时,实际上之前使用的autorelease方法是不能使用的,另外也不能使用NSAutoreleasePool类。虽然在在ARC有效时,autorelease不能使用,但其功能是可以通过其他方式来实现的,就如现在的_ _autoreleasing。
//ARC无效时 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id obj = [[NSObject alloc]init]; [obj autorelease]; [pool drain]; //ARC有效时,上述源代码就可以写成如下了: @autoreleasePool { id _ _autoreleasing obj = [[NSObject alloc] init]; }
也就是说,在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有_ _autoreleasing 修饰符替代autorelease方法
对应图.png但是显式的添加_ autorelease修饰符同显式地附加 _strong一样罕见
1.前面提到过,通过调用alloc/new/copy/mutablecopy以外的方法取得对象,对象会被自动添加到autoreleasePool当中,即使不在变量后添加_ _autoreleasing修饰符
2.对象作为函数的返回值,编译器会将其自动添加到autoreleasePool(这一条不是很确定)下面简单举例:
+(id) array{ id obj = [[NSObject alloc] init]; return obj; }
因为没有显式添加所有权修饰符,所以obj其实是默认添加了_ _ strong 的。由于return使得对象超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为返回值,编译器会自动的将其添加到autoreleasePool.
3.虽然_ weak修饰符是为了避免循环引用的,但其实访问附有 _weak修饰符的变量时,实际上必定要访问到注册到autoreleasePool的对象。
因为_ _weak修饰符只支持对象的弱引用,而在访问引用对象的过程中,该对象可能随时被废弃,如果把要访问的对象注册到autoreleasePool中,那么在@autoreleasepool块结束之前都能确保该对象的存在,而不被释放。
2.ARC规则
- 不能使用retain/release/retain/retainCount/autorelease
- 不能使用NSAllocateObject、NSDealloccateObject
- 须遵守内存管理的方法命名规则
- 不要显式调用dealloc
- 使用@autoreleasepool 块替代NSAutorePool
- 不能使用区域NSZone
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显式转换" id "和" void * "
下面简单介绍部分规则:
1.须遵守内存管理的方法命名规则:在ARC有效时,用于对象生成/持有方法必须遵守以下的命名规则
- alloc
- new
- copy
- mutableCopy
以上述名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。
2.对象型变量不能作为C语言结构体(struct/union)的成员
struct Data { NSMutableArray *array; }
以上这种方式,编译就会报错。虽然是LLVM3.0,但是无论怎样,C语言的规约上还是没有方法来管理结构体成员的生命周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生命周期,但是对于C语言结构体来说,这在标准上是不可实现的。
要把对象类型的变量加入到结构体成员中时,可强制转换为void *,或是附加前面所述的_ _unsafe_unretained修饰符即可。
3.ARC中的属性
- 属性种类:
属性声明种类 | 所有权修饰符 |
---|---|
assign | _ _unsafe_unretained |
copy | _ _strong修饰符(但是赋值的是被复制的对象) |
retain | _ _strong修饰符 |
strong | _ _strong修饰符 |
unsafe_unretained | _ _unsafe_unretained修饰符 |
weak | _ _weak修饰符 |
以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量。只有copy属性不是简单的赋值,他赋值是通过NSCoping接口的copyWithZone方法复制赋值源所生成的对象。
_ unsafe_unretained修饰符以外的 strong/ weak/ autorealease修饰符保证其指定的变量初始化为nil。同样的,附有 strong/ weak/ _autorealease修饰符变量的数组也可以保证其初始化为nil
4.ARC的实现(_ _strong)
苹果官方说明中称,ARC是“由编译器进行内存管理”的,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时的协助,也就是说,ARC由以下工具库来实现(iOS5之后)
- clang(LLVM编译器)3.0及以上
- obj4 Objective-C运行时库493.9以上
围绕着clang汇编输出和objc4库(主要是runtime/objc-arr.mm)的源代码进行说明
-
_ strong修饰符(研究赋值给附有 _strong修饰符的变量在实际的程序中到底是怎么运行的)
{ id _ _strong obj = [[NSObject alloc] init]; }
在编译器选项“-S”的同时运行clang,可取得程序汇编输出,看看汇编输出和obj4库的源代码就能知道程序是如何工作的。该源代码实际上可以转化为调用以下的函数,为了方便理解,都将使用模拟源代码。
//编译器模拟的代码 id obj = objc_msgSend(NSObject,@selector(alloc)); objc_msgSend(obj,@selector(init)); objc_release(obj);
如源代码所示,2次调用objc_msgSend方法(alloc和init方法),变量作用域结束时,通过objc_release释放对象。虽然ARC有效时不能使用release方法,但由此推测编译器编译时自动插入了release
参考文献:
- 《Pro multithreading and memory management for iOS and OS X》