OC中的内存管理其二:ARC
所有权修饰符
在《OC中的@property及属性修饰符》一章中,我已经总结了OC中的属性修饰符的各种作用,我们知道,在ARC环境下,我们定义一个局部变量时,由于它不属于成员变量,因此所有权修饰符为默认的strong,但如果我们希望它使用其他类型的属性修饰,可以利用所有权修饰符来更改它的所有权类型。
我们首先来定义一个id类型的变量。
id obj = [[NSObject alloc] init];
当我们这样定义一个变量时,实际上在ARC环境下它等效于:
id __strong obj = [[NSObject alloc] init];
这里的__strong便是一个所有权修饰符,所有权修饰符一共有如下四种:
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
前两者我们已经在前文中总结过,这里只介绍后两者。
__unsafe_unretained
从名字上看这似乎是一个新的所有权类型,但事实上它和assign并无差异,被这个类型修饰意味着这个变量将不受引用计数管理,适用范围仅在ios4及以前的系统中,在ARC下我们完全可以用weak来替代它。
__autoreleasing
在ARC中,指针会在超过作用域后自动释放引用,因此我们不能主动使用release来释放引用,也不能使用NSAutoreleasePool对象限定自动释放的空间,但在某些情况下,我们确实希望使用NSAutoreleasePool来节省内存,这时,我们可以使用@autoreleasepool关键字来替代原本的NSAutoreleasePool,使用方法如下:
@autoreleasepool{
MyObject __autoreleasing *obj = [[MyObject alloc]init];
}
这种写法等价于MRC下的
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
MyObject *obj = [[MyObject alloc]init];
[obj autorelease];
[pool drain];
可以看出ARC下的写法更为简洁直观,不过实际上我们也不必显示的写出__autoreleasing,在autoreleasepool中,即使我们用strong来定义变量,也不难想到,在超过autoreleasepool作用域后ARC会使局部变量自动释放对象的引用。而当我们用__weak修饰变量时,编译器必须确保对象注册到autoreleasepool,以保证对象至少在pool中不会被废弃(dealloc),这样__weak修饰的变量就不会失去它指向的对象。
因此在ARC下:
@autoreleasepool{
MyObject __weak *obj1 = obj0;
NSLog(@"%@", [obj1 class]);
}
等价于
@autoreleasepool{
MyObject __weak *obj1 = obj0;
MyObject __autoreleasing *tmp = obj1; //编译器自动添加
NSLog(@"%@", [tmp class]); //编译器自动更改
}
ARC下不能做的事
###不能使用retain/release/retainCount/autorelease
在ARC下使用这四种消息都会引起编译报错。 这四种消息中的三种我们已经在之前提过,并且我们很清楚在系统会自动释放对象的前提下我们是不能插手去主动释放的道理,但retainCount可以返回对象当前的引用计数,为什么也不能使用了呢?原因如下:
它所返回的保留计数只是在某个给定时间点上的值,并未考虑到系统会稍后将自动释放池清空,因此返回的引用计数很大可能并非是真实的。
事实上retainCount对我们的程序不能起到任何作用,大概是因为苹果考虑到开发者使用retainCount只会让程序变得更容易出错,因此索性禁用了这个方法吧。
不能使用NSZone
目前我个人对NSZone也只能说是一知半解,已知的是
- NSZone是一个包括了一系列用于分配和释放内存的函数的结构体
- 其中有NSCreateZone(…)、NSZoneMalloc(…)、NSZoneCalloc(…)等等
- 与Malloc相比,NSZone在分配内存时将内存划分为了若干部分(Zone),其中大的部分用来存放占用空间大的对象,小的部分则用来装小的对象,这样可以更充分的利用内存空间,减少碎片的出现
- 但在如今内存管理已经极具效率,已经不再需要NSZone,使用NSZone反而带来负面影响,因此在ARC下被禁用。
如上所述,使用NSZone为何会带来负面影响尚且不知,不过大体意思就是这已经是我们不再需要关心的问题,不要再用就是了。
不能使用NSAllocateObject/NSDeallocateObject
书中并没有写明为何不能使用这两个函数,只提到如果使用就会在编译时报错。
实际原因是NSAllocateObject申请内存的方式是通过NSZoneMalloc,由于NSZone被禁用,因此它也理所当然的被禁用了。
必须遵守内存管理的方法命名规则
ARC相比MRC的命名规则只是多了一条。
- init
除了必须返回对象之外,它还必须要是实例方法,因为init只是对申请了内存的对象进行初始化。
编译器不会监督我们这么做,这应该是属于我们的自我修养。
不要显式调用dealloc
同上,编译器不会报错,但dealloc应当由系统自动调用。
显示转换id与void *
首先提一下void *和id的区别
void *这种类型的指针被系统认为可能指向任何数据类型。
而id是OC对象类型的指针,因此我们可以对id类型发送消息。
而在向void *类型发送消息时,编译器会报错,即使它指向的是OC对象。
在MRC下
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
这样是OK的。
但在ARC下会编译报错,提示非OC对象与OC对象不能这样转换,这种情况我们必须使用__bridge转换。
__bridge转换涉及CF框架的知识,这部分内容我还没有具体学习,因此暂时先不深入总结,总之上述情况在ARC下应当写为:
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
不能将对象型变量作为C的结构体成员
直接这样做是不行的,编译报错。
原因是一旦成为了结构体成员,对象的生存周期将无法控制,C语言并未对结构体成员 的生存周期进行管理。
但我们可以将对象型变量转为void *类型或是加上__unsafe__unretained加入结构体成员,但它并不在ARC下。
最后
到此为止对内存管理的内容做了简单的总结,实际上书中还有一大半作者对关于苹果的实现做出的介绍我并未在文中总结,主要是因为自己理解的有限,以后还会再补充。