ARC下,对象什么时候加入自动释放池

ARC

ARC (Automatic Reference Counting) 是由编译器跟运行时共同完成的(运行时标记);编译器会在编译时会自动加上 retain、release、autorelease、dealloc 操作。

__autoreleasing

如果一个变量被用关键字修饰 __autoreleasing 修饰,那么变量会立即加入到自动释放池中

ARC规则

  1. 若方法名以下列词语开头,则其返回的对象归调用者所有:
    alloc new copy mutableCopy。归调用者所有的意思是:调用上述四种方法的那段代码要负责释放方法所返回的对象。

  2. 除了会自动调用“保留”与“释放”方法外,ARC 还可以执行一些手工操作很难甚至无法完成的优化。如果发现在同一个对象上执行多次“保留”与“释放”操作,那么ARC有时可以成对地移除这两个操作。
    一般,在方法中返回自动释放的对象时,要执行一个特殊函数。此时不直接调用对象的 autorelease 方法,而是改为调用 objc_autoreleaseReturnValue 。此函数会检视当前方法返回之后即将要执行的那段代码。若发现那段代码在返回的对象上执行 retain 操作,则设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位而不执行 autorelease 操作。与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行 retain,而是改为执行 objc_retainAutoreleaseReturnValue 函数。此函数要检测刚才提到的那个标志位,若已经置位,则不执行 retain 操作。设置并检测标志位,要比调用 autorelease 和 retain 更快。
    备注:objc_autoreleaseReturnValue 优化不一定开启,会根据不同CPU类型决定
    另外,这个标记位存在哪里呢?关键字:线程局部存储(TLS)

objc_autoreleaseReturnValue 相关代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Prepare a value at +1 for return through a +0 autoreleasing convention.
id objc_autoreleaseReturnValue(id obj) {
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
return objc_autorelease(obj);
}

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
ASSERT(getReturnDisposition() == ReturnAtPlus0);
//callerAcceptsOptimizedReturn 是个条件编译选项,不同CPU类型代码完全不一样
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法名以关键字 new 开头,ARC 不会加入 retain、release 或 autorelease 语句。
+ (VCHPerson *)newPerson {
VCHPerson *person = [[VCHPerson alloc] init];
return person;
}
// 方法名不以关键字开头,ARC 会自动加上 autorelease 语句。
+ (VCHPerson *)somePerson {
VCHPerson *person = [[VCHPerson alloc] init];
return person;
}
// ARC 会在函数末尾给 personOne 加上 release 语句。
// 而 somePerson 已经在方法内部加入到释放池中了
- (void)doSomething {
VCHPerson *personOne = [VCHPerson newPerson];
VCHPerson *personTwo = [VCHPerson somePerson];
}

内部的实现逻辑可以用以下代码代替

1
2
3
4
5
6
7
8
9
//非关键字开头
+ (instancetype)object {
return [[NSObject alloc] init];
// 实际会变成
return objc_autoreleaseReturnValue([[NSObject alloc] init]);
}
NSObject *object = [NSObject object];
// 实际会变成
NSObject *object = objc_retainAutoreleasedReturnValue([NSObject object]);

再来看看汇编

一、外部方法非关键字开头,内部方法关键字开头
  1. NSMutableArray 以 alloc 方式生成对象,引用计数为1,此时没有加入到自动释放池中
  2. init方法不做任何处理,猜测是编译器行为
  3. 当前方法名不以关键字开头,return时需要加入到自动释放池中,表示当前方法内持有对象,并负责释放对象。

但是由于编译器优化,不会立即加入到自动释放池中,而是调用 objc_autoreleaseReturnValue 方法标记对象(从下面的汇编代码可以看出确实调用了 objc_autoreleaseReturnValue 方法)。
如果对象返回后,又有别的变量需要 retain 这个对象,则编译器会调用这个方法 objc_retainAutoreleasedReturnValue,此时会检查对象的是否已经被标记,如果已经被标记,则相互抵消,并将标记位清除,如果没有被标记则最终调用 retain 方法。

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
- (id)getObjectWithAlloc {
id obj = [[NSMutableArray alloc] init];
return obj;//断点
/*
VCHTest`-[AppDelegate getObjectWithAlloc]:
0x10027e210 <+0>: pushq %rbp
0x10027e211 <+1>: movq %rsp, %rbp
0x10027e214 <+4>: subq $0x20, %rsp
0x10027e218 <+8>: movq %rdi, -0x8(%rbp)
0x10027e21c <+12>: movq %rsi, -0x10(%rbp)
0x10027e220 <+16>: movq 0x9c41(%rip), %rdi ; (void *)0x00007fff801e6298: NSMutableArray
0x10027e227 <+23>: callq 0x10027f814 ; symbol stub for: objc_alloc_init
0x10027e22c <+28>: movq %rax, -0x18(%rbp)
-> 0x10027e230 <+32>: movq -0x18(%rbp), %rdi
0x10027e234 <+36>: movq 0x3df5(%rip), %rax ; (void *)0x00007fff20191840: objc_retain
0x10027e23b <+43>: callq *%rax
0x10027e23d <+45>: xorl %ecx, %ecx
0x10027e23f <+47>: movl %ecx, %esi
0x10027e241 <+49>: leaq -0x18(%rbp), %rdi
0x10027e245 <+53>: movq %rax, -0x20(%rbp)
0x10027e249 <+57>: callq 0x10027f86e ; symbol stub for: objc_storeStrong
0x10027e24e <+62>: movq -0x20(%rbp), %rdi
0x10027e252 <+66>: addq $0x20, %rsp
0x10027e256 <+70>: popq %rbp
0x10027e257 <+71>: jmp 0x10027f826 ; symbol stub for: objc_autoreleaseReturnValue
*/
}
二、外部方法非关键字开头,内部方法非关键字开头
  1. NSMutableArray 以 array 方式生成对象,引用计数为1,并加入到自动释放池中(标记)
  2. 调用 objc_retainAutoreleasedReturnValue,因为临时变量需要持有对象,此时标记位清除
  3. 由于方法名不以关键字开头,return 时调用 objc_autoreleaseReturnValue 加入到自动释放池中(同样也是先标记)
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
29
30
31
32

- (id)getObjectWithArray {
id obj = [NSMutableArray array];
return obj;//断点
/*
VCHTest`-[AppDelegate getObjectWithArray]:
0x10027e260 <+0>: pushq %rbp
0x10027e261 <+1>: movq %rsp, %rbp
0x10027e264 <+4>: subq $0x20, %rsp
0x10027e268 <+8>: movq %rdi, -0x8(%rbp)
0x10027e26c <+12>: movq %rsi, -0x10(%rbp)
0x10027e270 <+16>: movq 0x9bf1(%rip), %rdi ; (void *)0x00007fff801e6298: NSMutableArray
0x10027e277 <+23>: movq 0x9aba(%rip), %rsi ; "array"
0x10027e27e <+30>: movq 0x3d9b(%rip), %rax ; (void *)0x00007fff20175280: objc_msgSend
0x10027e285 <+37>: callq *%rax
0x10027e287 <+39>: movq %rax, %rdi
0x10027e28a <+42>: callq 0x10027f85c ; symbol stub for: objc_retainAutoreleasedReturnValue
0x10027e28f <+47>: movq %rax, -0x18(%rbp)
-> 0x10027e293 <+51>: movq -0x18(%rbp), %rdi
0x10027e297 <+55>: movq 0x3d92(%rip), %rax ; (void *)0x00007fff20191840: objc_retain
0x10027e29e <+62>: callq *%rax
0x10027e2a0 <+64>: xorl %ecx, %ecx
0x10027e2a2 <+66>: movl %ecx, %esi
0x10027e2a4 <+68>: leaq -0x18(%rbp), %rdi
0x10027e2a8 <+72>: movq %rax, -0x20(%rbp)
0x10027e2ac <+76>: callq 0x10027f86e ; symbol stub for: objc_storeStrong
0x10027e2b1 <+81>: movq -0x20(%rbp), %rdi
0x10027e2b5 <+85>: addq $0x20, %rsp
0x10027e2b9 <+89>: popq %rbp
0x10027e2ba <+90>: jmp 0x10027f826 ; symbol stub for: objc_autoreleaseReturnValue
*/
}
三、外部方法关键字开头,内部方法关键字开头
  1. NSMutableArray 使用 alloc 生成对象,引用计数为1,内部没有加入 AutoreleasePool 中;
  2. 随后调用 init 方法,这里 init 方法返回对象时没有加入到自动释放池中,猜测是编译器行为;
  3. 由于当前方法以 copy 开头,return 时既不调用 release,也不调用 autorelease;
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
- (id)copyObjectWithAlloc {
id obj = [[NSMutableArray alloc] init];
return obj;//断点
/*
VCHTest`-[AppDelegate copyObjectWithAlloc]:
0x10027e160 <+0>: pushq %rbp
0x10027e161 <+1>: movq %rsp, %rbp
0x10027e164 <+4>: subq $0x20, %rsp
0x10027e168 <+8>: movq %rdi, -0x8(%rbp)
0x10027e16c <+12>: movq %rsi, -0x10(%rbp)
0x10027e170 <+16>: movq 0x9cf1(%rip), %rax ; (void *)0x00007fff801e6298: NSMutableArray
0x10027e177 <+23>: movq %rax, %rdi
0x10027e17a <+26>: callq 0x10027f814 ; symbol stub for: objc_alloc_init
0x10027e17f <+31>: movq %rax, -0x18(%rbp)
-> 0x10027e183 <+35>: movq -0x18(%rbp), %rdi
0x10027e187 <+39>: callq *0x3ea3(%rip) ; (void *)0x00007fff20191840: objc_retain
0x10027e18d <+45>: xorl %ecx, %ecx
0x10027e18f <+47>: movl %ecx, %esi
0x10027e191 <+49>: leaq -0x18(%rbp), %rdi
0x10027e195 <+53>: movq %rax, -0x20(%rbp)
0x10027e199 <+57>: callq 0x10027f86e ; symbol stub for: objc_storeStrong
0x10027e19e <+62>: movq -0x20(%rbp), %rax
0x10027e1a2 <+66>: addq $0x20, %rsp
0x10027e1a6 <+70>: popq %rbp
0x10027e1a7 <+71>: retq
*/
}
四、外部方法关键字开头,内部方法非关键字开头
  1. NSMutableArray 使用 array 生成对象,引用计数为1,内部已经加入 AutoreleasePool 中(其实还没有加入,只是被标记了而已);
  2. 通过下面的汇编代码可以看出,方法内调用了 objc_retainAutoreleasedReturnValue (因为有个临时变量需要持有当前对象),此时发现该对象已经被标记,则相互抵消,清除标志位,不加入自动释放池中;
  3. 由于当前方法以 copy 开头,return 时既不调用 release,也不调用 autorelease;
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
29
30
- (id)copyObjectWithArray {
id obj = [NSMutableArray array];
return obj;//断点
/*
VCHTest`-[AppDelegate copyObjectWithArray]:
0x10027e1b0 <+0>: pushq %rbp
0x10027e1b1 <+1>: movq %rsp, %rbp
0x10027e1b4 <+4>: subq $0x20, %rsp
0x10027e1b8 <+8>: movq %rdi, -0x8(%rbp)
0x10027e1bc <+12>: movq %rsi, -0x10(%rbp)
0x10027e1c0 <+16>: movq 0x9ca1(%rip), %rax ; (void *)0x00007fff801e6298: NSMutableArray
0x10027e1c7 <+23>: movq 0x9b6a(%rip), %rsi ; "array"
0x10027e1ce <+30>: movq %rax, %rdi
0x10027e1d1 <+33>: callq *0x3e49(%rip) ; (void *)0x00007fff20175280: objc_msgSend
0x10027e1d7 <+39>: movq %rax, %rdi
0x10027e1da <+42>: callq 0x10027f85c ; symbol stub for: objc_retainAutoreleasedReturnValue
0x10027e1df <+47>: movq %rax, -0x18(%rbp)
-> 0x10027e1e3 <+51>: movq -0x18(%rbp), %rdi
0x10027e1e7 <+55>: callq *0x3e43(%rip) ; (void *)0x00007fff20191840: objc_retain
0x10027e1ed <+61>: xorl %ecx, %ecx
0x10027e1ef <+63>: movl %ecx, %esi
0x10027e1f1 <+65>: leaq -0x18(%rbp), %rdi
0x10027e1f5 <+69>: movq %rax, -0x20(%rbp)
0x10027e1f9 <+73>: callq 0x10027f86e ; symbol stub for: objc_storeStrong
0x10027e1fe <+78>: movq -0x20(%rbp), %rax
0x10027e202 <+82>: addq $0x20, %rsp
0x10027e206 <+86>: popq %rbp
0x10027e207 <+87>: retq
*/
}

测试

下面代码执行会怎样

1
2
3
4
5
6
7
8
9
10
11
id __unsafe_unretained obj0 = nil;
id __unsafe_unretained obj1 = nil;
{
NSArray *array0 = [self getObjectWithAlloc];
obj0 = array0;
//
NSArray *array1 = [self copyObjectWithAlloc];
obj1 = array1;
}
NSLog(@"vhuichen obj0 = %@", obj0);
NSLog(@"vhuichen obj1 = %@", obj1);