目录
1. 引用计数
在ARC时代,为什么我们还要理解MRC下的引用计数?原因主要有二:
-
ARC并非万能。理解引用计数是我们解决循环引用的基础;
-
了解引用计数可以加深对OC内存管理的理解。
1.1 MRC vs ARC
新开1个工程,在Build Phases的AppDelegate.m
里添加-fno-objc-arc
,可以开启MRC模式。
输入如下代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// AppDelegate.m
#define isARC 1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#if isARC == 0 //-fno-objc-arc
NSObject *object = [[NSObject alloc] init];
NSLog(@"Reference Count = %u", [object retainCount]); //1
NSObject *anotherObject = [object retain];
NSLog(@"Reference Count = %u", [anotherObject retainCount]); //2
[anotherObject release];
NSLog(@"Reference Count = %u", [anotherObject retainCount]); //1
[object release];
NSLog(@"Reference Count = %u", [anotherObject retainCount]); //1, why not 0?
#elif isARC == 1
NSObject *object = [[NSObject alloc] init];
NSLog(@"Reference count = %ld", CFGetRetainCount((__bridge CFTypeRef)object)); //1
//[object retain];
NSObject *anotherObject = object;
NSLog(@"Reference count = %ld", CFGetRetainCount((__bridge CFTypeRef)object)); //2
anotherObject = nil;
//[object release];
NSLog(@"Reference count = %ld", CFGetRetainCount((__bridge CFTypeRef)object)); //1
object = nil;
//[object release];
NSLog(@"Reference count = %ld", CFGetRetainCount((__bridge CFTypeRef)object)); //Thread 1: EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0X0)
#endif
return YES;
}
在MRC模式下(-fno-objc-arc
),我们可以看到:
1.在实例化NSObject
时,object
指向了该实例,因此该实例retainCount
从0变为1。
2.anotherObject
也指向该实例,并且retain
,则retainCount
从1变为2。如果是用=object
而不是=[object retain]
则retainCount
依旧是1。
3.anotherObject release
使得该实例retainCount
-1。
4.object release
使得该实例retainCount
-1变为0释放。但是为什么retainCount
输出还是1呢?因为该对象的内存已经被回收,而我们向一个已经被回收的无效对象发了一个retainCount
消息,所以它的输出结果应该是不确定的。不将无效对象的这个值从1变成0,可以减少一次内存的写操作,加速对象的回收。
在ARC模式下(删除-fno-objc-arc
),retain和release是由系统自动加入到适当的位置的:
-
实例化过程引用计数和MRC一样。若增加
__weak
修饰符,则NSObject
实例之初就会被释放。 -
这和3一样。
-
object = nil
会使实例retainCount
减1。这里减1后为0。 -
向已释放的实例发送
CFGetRetainCount((__bridge CFTypeRef)object)
(ARC下retainCount
已被禁止)会触发野指针错误。
2 ARC4种所有权修饰符
属性修饰符对应的4种所有权修饰符关系如下。
2.1 __weak
表示弱引用,对应属性修饰符weak
。弱应用对对象的生命周期无影响。对象在被释放的同时,指向它的弱引用会被自动置为nil,这个技术称为zeoring weak pointer。这样有效地防止了野指针的产生。__weak
用在防止循环引用的场景中(如delegate,block的weak strong dance和无效的强引用(如@IBOutlet,viewController->view->subView,没有必要再viewController->subview)。
2.2 __strong
表示强引用,对应属性修饰符strong
和copy
。当所有对象没有任何一个强引用时,才会被释放。如果在声明时不加修饰符,默认是强应用。当需要释放强引用指向的对象时,需要将强引用置nil。
2.3 __autoreleasing
__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的情况下。具体见Autoreleasing。
2.4 __unsafe_unretained
和__weak一样,但对象释放后,不会自空,所以非常不安全。由于ARC是在iOS 5引入的,而这个修饰符主要是兼容iOS 4以及更低版本的设备,现在可以完全忽略这个修饰符了,因为iOS 4早已退出历史舞台很多年。
2 ARC下注意事项
2.1 Retain Cycle 问题及解决
利用instrument leak
1
2
3
4
NSMutableArray *array1 = [NSMutableArray new];
NSMutableArray *array2 = [NSMutableArray new];
[array1 addObject:array1];
[array2 addObject:array1];
当有如上引用循环时,可以利用instrument的leak来查看,具体步骤如下:
-
Xcode -> Product -> Profile -> Leak;
-
RecordingCycles & Roots会清楚显示循环引用的对象。
3 总结
本文总结成下图。