objc作为极少见的以引用记数来管理内存的动态语言,对于从其它语言转过来的开发者来说有 点陌生,甚至是对于MRC或者ARC有点束手无策,网上的文章一般不能透彻的分析其中的奥妙, 还好《objc高级编程》这本书为我们打开了objc引用记数的神秘面纱,这里将这本书中关于的 内存管理方面的精髓做个总结。
MRC
MRC(Manual Reference Counting),顾名思义即手动管理对象的引用记数,在xcode4.2之前, ios开发者都需要自己来管理对象的生命周期,其有四个准则:
自己生成的对象自己持有
{ id obj1 = [[NSObject alloc] init]; id obj2 = [[NSObject new]; id obj3 = [[NSObject copy]; id obj4 = [[NSObject mutableCopy]; }
这四种方式(alloc/new/copy/mutableCopy,以及以他们开头的函数簇)生成的对象是自己 生成并持有的对象。
非自己生成的对象自己也能持用
{ id obj = [NSArray array]; [obj retain]; }
对于非自己生成的对象,需要调用retain来持有该对象。
不需要自己持有的对象时释放
{ id obj = [[NSObject alloc] init]; [obj release]; }
如果自己持有的对象不再需要,使用release释放。
无法释放非自己持有的对象
{ id obj = [NSArray array]; [obj release]; }
上述release调用会导致奔溃,因为释放非自己持有的对象是非法的。
实现上述引用记数(垃圾回收的一种)有两种通用的方式,如下:
- 将引用记数(retainCount)保存在对象头中,调用alloc或者retain之后,retainCount +1,调用release之后,retainCount-1,当引用记数为0时,调用对象的dealloc方法来释 放当前的对象。
- 将引用记数和内存块地址保存在散列表(即引用记数表)中,引用记数的作用同上,内存 块地址用来追溯对象的内存地址。
苹果的实现方式为第2种方式,显然这种方式有利于调试和追溯内存空间。
* 谈到objc的内存管理就不得不说autoRelease(自动release),其管理内存的方式如下:
{
id obj = [[NSObject alloc] init];
[obj autorelease];
}
当autorelease调用发生时,obj对象会被注册进对应的autoreleasePool中,NSRunloop
此次循环结束的时候将pool中的对象废弃。所以对于注册进autoreleasePool中的对象,
并不是立即废弃,而是需要等循环结束来释放pool。
ARC
ARC(Automatic Reference Counting),即是编辑器帮助我们自动管理引用记数,大大地提升 了开发者的效率,是现在xcode的默认管理内存方式,其是通过给对象加上所有权修饰符来实 现的,一共有四种修饰符号:
__strong修饰符(id类型和对象类型默认的修饰符号)
{ id __strong obj0 = [[NSObject alloc] init];/*对象A*/ id __strong obj1 = [[NSObject alloc] init];/*对象B*/ obj0 = obj1;/*obj0持有对象B的强引用,因此原先的对A强引用失效,故而废弃对象A*/ }
由此可见,__strong不止在变量作用域,在赋值上也能正确管理对象所有者。那么ARC是如何 来实现自动管理的呢?
{ id obj = [[NSObject alloc] init]; /*编译器模拟代码*/ id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_release(obj); }
{ id obj = [NSArray array]; /*编译器模拟代码*/ id obj = objc_msgSend(NSArray, @selector(array)); objc_retainAutoreleaseReturnValue(obj); objc_release(obj); + (id) array { return [[NSArray alloc] init]; } /*编译器模拟代码*/ + (id) array { id obj = objc_msgSend(NSArray, @selector(alloc)); objc_msgSend(obj, @selector(init)); return objc_autoreleaseReturnValue(obj); } }
上面两个代码块,清晰得展现除了__strong的模拟实现过程,即ARC会在适当的地方自动插入 release代码。特别注意上述参数传递过程的实现,中间借助autoreleasePool来过度。
__weak修饰符(为了避免环形引用)
id __weak obj1; { id obj0 = [[NSObject alloc] init]; obj1 = obj0; } /* 此处obj1为nil,因为对象的强引用为0时即被释放 */
weak修饰符使我们能够使用对象,但是不持有对象。ARC是通过用weak表来记录weak变 量的,weak表与上文中的引用记数表相似,都是散列表的实现。一个对象被收回时,其在 weak表中的所有weak记录全部被销毁,其weak变量被赋值为nil。所以大量的weak修饰必 然会损耗性能,所以只在有循环应用危险的时候使用weak才是正确姿势。
对于weak修饰符还有一个比较有意思的事情,就是:使用附有weak修饰符的变量就是使用 autoreleasePool中的变量,这一点描述如下:
```
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
/*编译器模拟代码*/
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);
}
```
由以上可以得知,所有附有weak修饰的变量的使用,均会被注册到autoreleasePool中,使用 几次就会被注册几次,所以过多的使用weak也会造成问题。所以恰当使用weak很重要。
- __unsafe_unretained修饰符
此修饰符与weak一样,不会持有对象,但其不属于编译器管理的对象,所以是不安全的, 使用较少(可以在ios5之前,代替weak的作用)。
- __autoreleasing修饰符
比较少用。其调用的效果和MRC下调用autorelease的效果相同。
bridge
Objective-c中的对象(Foundation)与Core Foundation的对象没有任何区别,在MRC中, 两者之间可以自由转换,不需要使用额外的cpu资源。但是使用ARC来管理对象的情况下, 需要使用显式的转换,而且转换的安全性有所下降。如下有几种bridge书写方式。
__bridge转换(赋值)
{ id obj = [[NSObject alloc] init]; void *p = (__bridge void *)obj; id o = (__bridge id)p; }
bridge_retained与bridge_transfer
{ id obj = [[NSObject alloc] init]; /*变量p(Core Foundation)也持有对象*/ void *p = (__bridge_retained void *)obj; /*被转换变量在被赋值给转换变量之后随之释放*/ id obj = (__bridge_transfer id)p; }
引用记数作为垃圾回收最原始的方式,在objc中得以发扬光大,由此可见“技术并没有优劣 之分,在进行足够的探索和优化之后也能发挥巨大的作用”。上面是总结了objc内存管理的 一些关键要领,只有从大的引用记数的方向去把握objc的实现方式,才能真正领悟它。