0%

从汇编角度看ARC中的所有权修饰符

环境:Xcode11

ARC

在MRC时代 id obj1 = obj; 这一行代码仅仅是赋值,不会引起obj对象引用计数的任何变化。而在ARC时代这一行代码会导致对象的引用计数+1。这背后究竟发生了什么?接下来让我们研究一下。

ARC:自动引用计数。不再需要手动调用 retain 、release、autorelease 等方法,而是由编译器在合适的地方自动添加。那么编译器又是怎么知道是添加retain还是添加autorelease或者weak的呢?这就需要所有权修饰符了。通过不同类型的所有权修饰符,编译器就知道该添加什么语句了。

ARC 有效时,id 类型和对象类型必须附加所有权修饰符,有如下几种:

  • __strong 修饰符
  • __weak 修饰符
  • __unsafe_unretained 修饰符
  • __autoreleasing 修饰符

其中 __strong 修饰符是OC对象类型的默认修饰符。

接下来从汇编角度看,当一个指针变量使用这些修饰符后,编译器会作何处理。

__strong 修饰符

对于下面的代码:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
@autoreleasepool {
id obj = [NSObject new];
{
id __strong obj1 = obj; //引用计数为2
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

对应汇编:

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
33
34
35
36
37
38
Ltmp0:
.loc 1 12 22 prologue_end ## WeakDemo/main.m:12:22
callq _objc_autoreleasePoolPush
Ltmp1:
.loc 1 13 18 ## WeakDemo/main.m:13:18
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rcx, %rdi
movq %rax, -40(%rbp) ## 8-byte Spill
callq *_objc_msgSend@GOTPCREL(%rip)
.loc 1 13 12 is_stmt 0 ## WeakDemo/main.m:13:12
movq %rax, -24(%rbp)
Ltmp2:
.loc 1 15 23 is_stmt 1 ## WeakDemo/main.m:15:23
movq -24(%rbp), %rdi
callq *_objc_retain@GOTPCREL(%rip)
leaq L__unnamed_cfstring_(%rip), %rcx
movq %rax, -32(%rbp)
.loc 1 16 29 ## WeakDemo/main.m:16:29
movq -32(%rbp), %rsi
.loc 1 16 11 is_stmt 0 ## WeakDemo/main.m:16:11
movq %rcx, %rdi
movb $0, %al
callq _NSLog
xorl %edx, %edx
movl %edx, %esi
Ltmp3:
.loc 1 17 9 is_stmt 1 ## WeakDemo/main.m:17:9
leaq -32(%rbp), %rdi
callq _objc_storeStrong
xorl %edx, %edx
movl %edx, %esi
.loc 1 18 5 ## WeakDemo/main.m:18:5
leaq -24(%rbp), %rdi
callq _objc_storeStrong
movq -40(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax

上述汇编对应的编译器的模拟源代码:

1
2
3
4
5
6
7
id obj = objc_msgSend(NSObject, "new");

id obj1 = objc_retain(obj); //插入objc_retain
NSLog(@"obj1:%@", obj1);
objc_storeStrong(&obj1, nil); //obj1作用域结束,释放对象,并将obj1置为nil。

objc_storeStrong(&obj, nil);

id __strong obj1 = obj; 这句代码实际上被编译器转换为:id obj1 = objc_retain(obj);

看一下函数objc_retain的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}

// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());

if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}

return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}

抛开那些判断,objc_retain函数最终调用了 retain 方法。对象的引用计数加 1 。这也就是我们常说的在 ARC 下将一个对象赋值给一个强引用的指针变量时,编译器会自动插入一条 retain 方法。

接着往下看,当obj1作用域结束后会执行一句objc_storeStrong(&obj1, nil);

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
33
34
35
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}

//objc_release的实现,基本上等价于调用 release方法。
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}

// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());

if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}

((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}

objc_storeStrong函数的作用:

retain新对象,将指针变量指向新对象,release旧对象。特别的,如果 obj 等于 nil ,上述逻辑代表着将指针变量赋值为 nil,并且 release之前指向的对象。因此 objc_storeStrong(&obj1, nil); 代表着 release obj1 指向的对象,并将obj1置为nil。

这也就是我们常说的在 ARC 下一个强引用的指针变量超出作用域被废弃时,编译器会自动插入一条 release 方法,另外其实还把强引用的指针变量置为nil了。

例子2:

将一个使用new方法创建的对象赋值给一个 __strong 修饰的指针变量

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
id __strong obj1 = [NSObject new]; //引用计数为1而不是2
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

汇编:

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
Ltmp0:
.loc 1 30 22 prologue_end ## WeakDemo/main.m:30:22
callq _objc_autoreleasePoolPush
Ltmp1:
.loc 1 32 30 ## WeakDemo/main.m:32:30
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rcx, %rdi
movq %rax, -32(%rbp) ## 8-byte Spill
callq *_objc_msgSend@GOTPCREL(%rip)
leaq L__unnamed_cfstring_(%rip), %rcx
.loc 1 32 23 is_stmt 0 ## WeakDemo/main.m:32:23
movq %rax, -24(%rbp)
.loc 1 33 29 is_stmt 1 ## WeakDemo/main.m:33:29
movq -24(%rbp), %rsi
.loc 1 33 11 is_stmt 0 ## WeakDemo/main.m:33:11
movq %rcx, %rdi
movb $0, %al
callq _NSLog
xorl %edx, %edx
movl %edx, %esi
Ltmp2:
.loc 1 34 9 is_stmt 1 ## WeakDemo/main.m:34:9
leaq -24(%rbp), %rdi
callq _objc_storeStrong
movq -32(%rbp), %rdi ## 8-byte Reload
.loc 1 35 5 ## WeakDemo/main.m:35:5
callq _objc_autoreleasePoolPop
xorl %eax, %eax

等价于:

1
2
3
id obj1 = objc_msgSend(NSObject, "new");
NSLog(@"obj1:%@", obj1);
objc_storeStrong(&obj1, nil);

编译器发现obj1可以持有对象,又是创建对象,于是不再 retain。

例子 3:

将一个使用myPerson方法创建的对象赋值给一个 __strong 修饰的指针变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
id __strong obj1 = [ARCPerson myPerson];
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

+ (instancetype)myPerson {
ARCPerson *p = [ARCPerson new];
return p;
}

汇编:

上述汇编对应的编译器的模拟源代码:

1
2
3
4
5
6
id obj1;
id tmp = objc_msgSend(ARCPerson, "myPerson");
tmp = objc_autoreleaseReturnValue(tmp); //走了优化路径,对象并没有注册到自动释放池
obj1 = objc_retainAutoreleasedReturnValue(tmp); //和上面不同,这里插入的是objc_retainAutoreleasedReturnValue,而不是objc_retain。由于上面生成的对象没有注册到自动释放池,所以这里也是走的优化路径并没有再retain对象
NSLog(@"obj1:%@", obj1);
objc_storeStrong(&obj1, nil); //释放对象,起到优化作用,这样就不需要等到runloop结束才释放

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Prepare a value at +1 for return through a +0 autoreleasing convention.
id
objc_autoreleaseReturnValue(id obj)
{
/**
prepareOptimizedReturn(ReturnAtPlus1)的作用就是
检测紧接着调用的是不是objc_retainAutoreleasedReturnValue或objc_unsafeClaimAutoreleasedReturnValue
是的话就往线程的TLS上写入ReturnAtPlus1即1.这样后面两个函数获取TLS上的值如果发现是1就知道走了优化路径了。
这种优化方式有点特别,平时我们可能用个全局变量标记一下,但这里使用的是线程的TLS区域,另外检测后续调用的函数名称也是吊到不行。
*/
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj; //优化路径,不将对象注册到自动释放池

return objc_autorelease(obj); //正常路径,注册到自动释放池中
}

// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
ASSERT(getReturnDisposition() == ReturnAtPlus0);

if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}

return false;
}


// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition
acceptOptimizedReturn()
{
ReturnDisposition disposition = getReturnDisposition();
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}

//读取TLS上RETURN_DISPOSITION_KEY的值
static ALWAYS_INLINE ReturnDisposition
getReturnDisposition()
{
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}

//往TLS上写数据RETURN_DISPOSITION_KEY--disposition
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

objc_retainAutoreleasedReturnValue实现:

1
2
3
4
5
6
7
id
objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj; //优化路径,不 retain而是直接返回对象。

return objc_retain(obj); //正常路径,retain,引用计数+1后,才返回对象。
}

objc_autoreleaseReturnValue会做一个优化,如果检测到后续紧接着调用了objc_retainAutoreleasedReturnValue,则往 TLS 写ReturnAtPlus1标识,表明返回的对象的内存管理有优化。此时objc_autoreleaseReturnValue不会再将生成的对象真的注册到自动释放池中而是直接返回。当执行objc_retainAutoreleasedReturnValue时,会先从TLS中取值检测是否等于ReturnAtPlus1,相等说明走的是优化路径则不 retain,避免重复retain。

因此以其他单词命名开头的方法创建并返回的对象,该对象本应该注册到自动释放池,但如果创建后马上就赋值给一个 __strong 修饰符的指针变量,此时系统会做一个优化,该对象将不再注册到自动释放池中,因此能够及时释放对象,起到优化作用。
note:使用系统的对象 id __strong obj1 = [NSArray array];可能看不到这种优化。

总结:将一个对象赋值给一个 __strong 修饰符的指针变量,编译器会视情况插入objc_retainobjc_retainAutoreleasedReturnValue 函数的调用。而当指针变量超出作用域后被销毁时,会插入objc_storeStrong(&obj, nil); 释放对象,并置为nil。

objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue是一对优化。

objc_retainAutoreleaseReturnValue 和 objc_unsafeClaimAutoreleasedReturnValue 是另一对优化。

如果优化成功,他们都避免了多余的注册到自动释放池的过程,加快了对象的释放。

__weak 修饰符

对于下面的代码:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
@autoreleasepool {
id obj = [NSObject new];
{
id __weak obj1 = obj;
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

对应的汇编:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Ltmp3:
.loc 1 12 22 prologue_end ## WeakDemo/main.m:12:22
callq _objc_autoreleasePoolPush
Ltmp4:
.loc 1 13 18 ## WeakDemo/main.m:13:18
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rdi
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq _objc_msgSend@GOTPCREL(%rip), %rcx
movq %rax, -56(%rbp) ## 8-byte Spill
callq *%rcx
.loc 1 13 12 is_stmt 0 ## WeakDemo/main.m:13:12
movq %rax, -24(%rbp)
Ltmp5:
.loc 1 15 28 is_stmt 1 ## WeakDemo/main.m:15:28
movq -24(%rbp), %rsi
leaq -32(%rbp), %rax
.loc 1 15 21 is_stmt 0 ## WeakDemo/main.m:15:21
movq %rax, %rdi
movq %rax, -64(%rbp) ## 8-byte Spill
callq _objc_initWeak
movq -64(%rbp), %rdi ## 8-byte Reload
movq %rax, -72(%rbp) ## 8-byte Spill
.loc 1 16 29 is_stmt 1 ## WeakDemo/main.m:16:29
callq _objc_loadWeakRetained
movq %rax, %rcx
Ltmp0:
.loc 1 16 11 is_stmt 0 ## WeakDemo/main.m:16:11
leaq L__unnamed_cfstring_(%rip), %rdi
xorl %edx, %edx
## kill: def $dl killed $dl killed $edx
movq %rax, %rsi
movb %dl, %al
movq %rcx, -80(%rbp) ## 8-byte Spill
callq _NSLog
Ltmp1:
jmp LBB0_1
LBB0_1:
.loc 1 0 11 ## WeakDemo/main.m:0:11
movq -80(%rbp), %rdi ## 8-byte Reload
.loc 1 16 11 ## WeakDemo/main.m:16:11
callq *_objc_release@GOTPCREL(%rip)
Ltmp6:
.loc 1 17 9 is_stmt 1 ## WeakDemo/main.m:17:9
leaq -32(%rbp), %rdi
callq _objc_destroyWeak
xorl %eax, %eax
movl %eax, %esi
.loc 1 18 5 ## WeakDemo/main.m:18:5
leaq -24(%rbp), %rdi
callq _objc_storeStrong
movq -56(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax

上述汇编对应的编译器的模拟源代码:

1
2
3
4
5
6
7
8
9
id obj = objc_msgSend(NSObject, "new");

id obj1 = objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
NSLog(@"obj1:%@", tmp);
objc_release(tmp);
objc_destroyWeak(&obj1);

objc_storeStrong(&obj, nil);

可以看到 id __weak obj1 = obj;,这句代码实际上被编译器转换为:id obj1 = objc_initWeak(&obj1, obj);。没有调用什么 retain 方法,这就是使用__weak修饰符不会导致对象引用计数加 1 的原因。

but,objc_initWeak函数又是干什么的,这里不做详述,有兴趣的可以查看 weak 的实现原理。这里简单说下objc_initWeak的作用就是将一个指针变量的地址登记到对象的 weak 表中。它的实现是:

1
2
3
4
5
6
7
8
9
10
11
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}

return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}

接着往下看,会发现 NSLog(@"obj1:%@", obj1);代码变成了三句:

1
2
3
id tmp = objc_loadWeakRetained(&obj1);  //将对象引用计数加 1
NSLog(@"obj1:%@", tmp);
objc_release(tmp); //将对象引用计数减 1

如果对象还可用时执行了id tmp = objc_loadWeakRetained(&obj1);,那么即使对象在其他线程被释放了,对象此刻也不会被销毁,能够保证NSLog(@"obj1:%@", obj1);方法在执行完前对象都是可用的。

以前这里是将对象注册到自动释放池的,可以参考:Why __weak object will be added to autorelease pool? 。经过测试发现在MRC中还是将对象注册到自动释放池的。这时代码等价于:

1
2
3
id tmp = objc_loadWeakRetained(&obj1);  //将对象引用计数加 1
objc_autorelease(tmp); //注册到自动释放池.注册到自动释放池仅仅是注册到自动释放池,不会对对象的引用计数产生影响。
NSLog(@"obj1:%@", tmp);

举个例子:

1
2
3
4
5
6
7
8
9
10
11
- (void)hello {
{
MRCCat *cat = [MRCCat new];
self.cat = cat;
NSLog(@"%@ 1", self.cat);
[cat release];
} //使用weak,cat居然还没有被销毁。因为被注册到自动释放池里去了
NSLog(@"%@ 2", self.cat);
[self.cat hello];
NSLog(@"%@ hello", self);
}

我们会发现在调用 [cat release]; 后,cat对象并没有马上销毁。

我们可以在NSLog(@"%@ 1", self.cat); 这一行打上断点,并执行 po _objc_autoreleasePoolPrint() :

1
2
3
4
5
6
7
8
objc[3377]: [0x7fcd70848230]    0x7fcd6ef0a5c0  UIWindow
objc[3377]: [0x7fcd70848238] 0x60000032c6c0 __NSArrayM
objc[3377]: [0x7fcd70848240] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848248] 0x7fcd6ef0a5c0 UIWindow
objc[3377]: [0x7fcd70848250] 0x60000032c8a0 __NSArrayM
objc[3377]: [0x7fcd70848258] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848260] 0x7fcd6ef09f20 ViewController
objc[3377]: ##############

此时AUTORELEASE POOLS里还没有MRCCat对象,接着在 [cat release]; 打上断点,再次执行 po _objc_autoreleasePoolPrint()

1
2
3
4
5
6
objc[3377]: [0x7fcd70848248]    0x7fcd6ef0a5c0  UIWindow
objc[3377]: [0x7fcd70848250] 0x60000032c8a0 __NSArrayM
objc[3377]: [0x7fcd70848258] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848260] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848268] 0x600000f747b0 MRCCat
objc[3377]: ##############

可以看到MRCCat被注册到自动释放池里去了。所以后面即使调用 [cat release]; cat对象也不会马上销毁。如果你在[self.cat hello]; 打上断点,再次执行 po _objc_autoreleasePoolPrint()

1
2
3
4
5
6
7
8
objc[3377]: [0x7fcd70848240]    0x7fcd6ef09f20  ViewController
objc[3377]: [0x7fcd70848248] 0x7fcd6ef0a5c0 UIWindow
objc[3377]: [0x7fcd70848250] 0x60000032c8a0 __NSArrayM
objc[3377]: [0x7fcd70848258] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848260] 0x7fcd6ef09f20 ViewController
objc[3377]: [0x7fcd70848268] 0x600000f747b0 MRCCat
objc[3377]: [0x7fcd70848270] 0x600000f747b0 MRCCat
objc[3377]: ##############

你会发现cat对象被注册了两次。

因此使用附有 __weak 修饰符的变量,等价于使用retain并注册到autoreleasepool中的对象。如果你某段代码需要多次使用该weak变量,最好先retain后赋值给一个变量然后使用该变量。要不然每次都会retain+注册到自动释放池。所幸的是现在几乎接触不到MRC的代码,并且在ARC中使用附有 __weak 修饰符的变量,系统也不会retain后再将其注册到autoreleasepool中了,而是在使用前调用retain,使用后调用release马上释放。

另外我们对上述代码稍作改动,添加一个@autoreleasepool:

1
2
3
4
5
6
7
8
9
10
11
- (void)hello {
@autoreleasepool {
MRCCat *cat = [MRCCat new];
self.cat = cat;
NSLog(@"%@ 1", self.cat);
[cat release];
} //这里cat就会被销毁了
NSLog(@"%@ 2", self.cat);
[self.cat hello];
NSLog(@"%@ hello", self);
}

就会发现cat对象在autoreleasepool块执行完成后马上就被销毁了。

继续,当作用域结束时,obj1 变量被废弃,因此编译器插入了一条objc_destroyWeak函数的调用。该方法会将 obj1弱引用指针变量从对象的 weak 表中移除(这里obj1变量先于obj对象废弃,因此obj1不会被置为nil)。

objc_destroyWeak 源码如下:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil); //
}

static id
storeWeak(id *location, objc_object *newObj) {
...

// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
...
return (id)newObj;
}


/**
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;

weak_entry_t *entry;

if (!referent) return;

if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer); //将referrer从entry中移除。
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}

if (empty) {
weak_entry_remove(weak_table, entry);
}
}

// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}

size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}

weak_unregister_no_lock 的作用:将弱引用指针变量从对象的weak表里移除。如果weak表中对象的weak_entry为空,则将entry也移除。

上述就是使用 __weak 修饰符修饰指针变量时,ARC 对其的处理。

再来看一个例子:

将new方法刚创建的对象赋值给一个__weak指针变量

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
id __weak obj1 = [NSObject new];
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

你可能知道打印的结果为 null,但它对应的汇编又是怎样的呢?

对应的汇编:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Ltmp3:
.loc 1 30 22 prologue_end ## WeakDemo/main.m:30:22
callq _objc_autoreleasePoolPush
Ltmp4:
.loc 1 32 28 ## WeakDemo/main.m:32:28
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rdi
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq _objc_msgSend@GOTPCREL(%rip), %rcx
movq %rax, -48(%rbp) ## 8-byte Spill
callq *%rcx
leaq -24(%rbp), %rcx
.loc 1 32 21 is_stmt 0 ## WeakDemo/main.m:32:21
movq %rcx, %rdi
movq %rax, %rsi
movq %rax, -56(%rbp) ## 8-byte Spill
movq %rcx, -64(%rbp) ## 8-byte Spill
callq _objc_initWeak
movq _objc_release@GOTPCREL(%rip), %rcx
movq -56(%rbp), %rdi ## 8-byte Reload
movq %rax, -72(%rbp) ## 8-byte Spill
callq *%rcx
movq -64(%rbp), %rdi ## 8-byte Reload
.loc 1 33 29 is_stmt 1 ## WeakDemo/main.m:33:29
callq _objc_loadWeakRetained
movq %rax, %rcx
Ltmp0:
.loc 1 33 11 is_stmt 0 ## WeakDemo/main.m:33:11
leaq L__unnamed_cfstring_(%rip), %rdi
xorl %edx, %edx
## kill: def $dl killed $dl killed $edx
movq %rax, %rsi
movb %dl, %al
movq %rcx, -80(%rbp) ## 8-byte Spill
callq _NSLog
Ltmp1:
jmp LBB0_1
LBB0_1:
.loc 1 0 11 ## WeakDemo/main.m:0:11
movq -80(%rbp), %rdi ## 8-byte Reload
.loc 1 33 11 ## WeakDemo/main.m:33:11
callq *_objc_release@GOTPCREL(%rip)
Ltmp5:
.loc 1 34 9 is_stmt 1 ## WeakDemo/main.m:34:9
leaq -24(%rbp), %rdi
callq _objc_destroyWeak
movq -48(%rbp), %rdi ## 8-byte Reload
.loc 1 35 5 ## WeakDemo/main.m:35:5
callq _objc_autoreleasePoolPop
xorl %eax, %eax

上述汇编对应的编译器的模拟源代码:

1
2
3
4
5
6
7
id tmp = objc_msgSend(NSObject, "new");
id obj1 = objc_initWeak(&obj1, tmp); //注册到对象的 weak 表中,这样对象销毁时指针变量会被置为 nil。
objc_release(tmp);
id tmp1 = objc_loadWeakRetained(&obj1);
NSLog(@"obj1:%@", tmp1);
objc_release(tmp1);
objc_destroyWeak(&obj1);

可以看到objc_initWeak后,马上调用了objc_release,导致对象被释放销毁,同时 obj1 会被置为 nil,于是后面的 tmp1 也是 nil。打印也是 nil。

对于代码id __weak obj1 = [NSObject new];,由于__weak 修饰的指针变量无法持有对象,因此对象一创建就被销毁。

另外一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {
@autoreleasepool {
id obj = [NSObject new];
id __weak ref = obj; //_objc_initWeak(内部调用的是storeWeak)
NSLog(@"ref-1:%@", ref);
{
id obj2 = [NSObject new];
ref = obj2; //_objc_storeWeak (内部调用的也是storeWeak)
NSLog(@"ref-2:%@", ref);
}
}
return 0;
}

总结

__weak 修饰符作用:

1.当我们将一个对象赋值给一个 __weak 修饰符的指针变量时,编译器会插入 objc_initWeakobjc_storeWeak 函数将该弱指针变量从旧对象的weak表剔除(如果之前有指向对象的话)并注册到新对象的weak表里,然后将弱指针指向新对象。

2.当使用附有 __weak 修饰符的变量时,编译器会在使用前插入 objc_loadWeakRetained 函数将对象引用计数+1,保证对象在后面的使用过程中不被释放。并在使用后插入 objc_release(tmp1); 释放对象。

3.当弱指针变量超出作用域被废弃时,编译器会插入 objc_destroyWeak 将该变量从对象的weak表里剔除。

4.当对象销毁时,对象的weak表中的所有弱指针变量会被赋值为nil,避免了野指针崩溃,比如对nil对象发送消息不会有任何反应,但是如果是对nil进行解引用还是会崩溃的。

这些功能是assign所不具备的,assign唯一与weak相同的地方就是不会导致对象的引用计数+1。

__autoreleasing修饰符

对于代码:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
@autoreleasepool {
id obj = [NSObject new];
{
id __autoreleasing obj1 = obj; //2
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

对应汇编:

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
33
Ltmp0:
.loc 1 30 22 prologue_end ## WeakDemo/main.m:30:22
callq _objc_autoreleasePoolPush
Ltmp1:
.loc 1 31 18 ## WeakDemo/main.m:31:18
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rcx, %rdi
movq %rax, -40(%rbp) ## 8-byte Spill
callq *_objc_msgSend@GOTPCREL(%rip)
.loc 1 31 12 is_stmt 0 ## WeakDemo/main.m:31:12
movq %rax, -24(%rbp)
Ltmp2:
.loc 1 33 30 is_stmt 1 ## WeakDemo/main.m:33:30
movq -24(%rbp), %rdi
callq _objc_retainAutorelease
leaq L__unnamed_cfstring_(%rip), %rcx
movq %rax, -32(%rbp)
.loc 1 34 29 ## WeakDemo/main.m:34:29
movq -32(%rbp), %rsi
.loc 1 34 11 is_stmt 0 ## WeakDemo/main.m:34:11
movq %rcx, %rdi
movb $0, %al
callq _NSLog
xorl %edx, %edx
movl %edx, %esi
Ltmp3:
.loc 1 36 5 is_stmt 1 ## WeakDemo/main.m:36:5
leaq -24(%rbp), %rdi
callq _objc_storeStrong
movq -40(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax

模拟代码:

1
2
3
4
id obj = msg_Send(NSObject, "new");
id obj1 = objc_retainAutorelease(obj); //retain对象+注册对象到自动释放池
NSLog(@"obj1:%@", obj1);
objc_storeStrong(&obj, nil);

objc_retainAutorelease实现:

1
2
3
4
5
id
objc_retainAutorelease(id obj)
{
return objc_autorelease(objc_retain(obj));
}

会先 retain 对象,再将对象注册到自动释放池里。

所以id __autoreleasing obj1 = obj; 执行完后对象的引用计数其实是 2。

例子 2:

创建一个对象并赋值给__autoreleasing 的obj1

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
id __autoreleasing obj1 = [NSObject new];
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

汇编:

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
Ltmp0:
.loc 1 30 22 prologue_end ## WeakDemo/main.m:30:22
callq _objc_autoreleasePoolPush
Ltmp1:
.loc 1 32 37 ## WeakDemo/main.m:32:37
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rcx, %rdi
movq %rax, -32(%rbp) ## 8-byte Spill
callq *_objc_msgSend@GOTPCREL(%rip)
.loc 1 32 30 is_stmt 0 ## WeakDemo/main.m:32:30
movq %rax, %rdi
callq _objc_autorelease
leaq L__unnamed_cfstring_(%rip), %rcx
movq %rax, -24(%rbp)
.loc 1 33 29 is_stmt 1 ## WeakDemo/main.m:33:29
movq -24(%rbp), %rsi
.loc 1 33 11 is_stmt 0 ## WeakDemo/main.m:33:11
movq %rcx, %rdi
movb $0, %al
callq _NSLog
movq -32(%rbp), %rdi ## 8-byte Reload
Ltmp2:
.loc 1 35 5 is_stmt 1 ## WeakDemo/main.m:35:5
callq _objc_autoreleasePoolPop
xorl %eax, %eax

模拟代码:

1
2
3
id obj1 = msg_Send(NSObject, "new");
objc_autorelease(obj1);
NSLog(@"obj1:%@", obj1);

objc_autorelease实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease(); //将对象注册到自动释放池
}

// Equivalent to [this autorelease], with shortcuts if there is no override
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}

return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}

如果是创建一个对象并赋值给__autoreleasing 的指针变量,则仅仅是将对象注册到自动释放池。

因此id __autoreleasing obj1 = [NSObject new];执行完成后,对象的引用计数为 1。

__unsafe_unretained 修饰符

代码:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
@autoreleasepool {
id obj = [NSObject new];
{
id __unsafe_unretained obj1 = obj;
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

对应的汇编:

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
33
Ltmp0:
.loc 1 30 22 prologue_end ## WeakDemo/main.m:30:22
callq _objc_autoreleasePoolPush
Ltmp1:
.loc 1 31 18 ## WeakDemo/main.m:31:18
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rcx, %rdi
movq %rax, -40(%rbp) ## 8-byte Spill
callq *_objc_msgSend@GOTPCREL(%rip)
leaq L__unnamed_cfstring_(%rip), %rcx
.loc 1 31 12 is_stmt 0 ## WeakDemo/main.m:31:12
movq %rax, -24(%rbp)
Ltmp2:
.loc 1 33 43 is_stmt 1 ## WeakDemo/main.m:33:43
movq -24(%rbp), %rax
.loc 1 33 36 is_stmt 0 ## WeakDemo/main.m:33:36
movq %rax, -32(%rbp)
.loc 1 34 33 is_stmt 1 ## WeakDemo/main.m:34:33
movq -32(%rbp), %rsi
.loc 1 34 13 is_stmt 0 ## WeakDemo/main.m:34:13
movq %rcx, %rdi
movb $0, %al
callq _NSLog
xorl %edx, %edx
movl %edx, %esi
Ltmp3:
.loc 1 36 5 is_stmt 1 ## WeakDemo/main.m:36:5
leaq -24(%rbp), %rdi
callq _objc_storeStrong
movq -40(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax

编译器模拟代码:

1
2
3
4
id obj = objc_msgSend(NSObject, "new");
id obj1 = obj;
NSLog(@"obj1:%@", obj1);
objc_storeStrong(&obj, nil);

可以看到仅仅是指针变量间的赋值,其他什么操作都没有。

示例 2:

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
id __unsafe_unretained obj1 = [NSObject new];
NSLog(@"obj1:%@", obj1);
}
}
return 0;
}

编译器模拟代码:

1
2
3
4
id tmp = objc_msgSend(NSObject, "new");
id obj1 = tmp;
objc_release(tmp);
NSLog(@"obj1:%@", obj1);

这一次会发生崩溃,因为obj1已经变成了一个野指针。

__unsafe_unretained的功能和 assign 是一样的,只不过assign 既可以修饰对象类型也可以修饰基础类型。注意:没有__assign.

ps:个人感觉虽然 ARC 的优点是在合适的地方自动帮我们添加 retain 和 release,但作为开发者的我们如果没有查看汇编代码并不好确定它究竟会在哪些地方添加以及添加的啥函数调用,所以一些奇怪的现象可能会让你困惑。如果是 MRC 的话,我们就很清楚一个对象的释放与持有,弊端就是麻烦并且容易忘记 release导致内存泄漏。两种方案各有利弊,但总的来说还是 ARC 要更好一些。

疑问

Q0:ARC 适用范围?是否仅对 OC 文件起作用?

这个没查到资料,也不敢确定,但个人感觉 ARC 开启后,编译器应该只对 OC 源文件做处理。毕竟 C++也有它自己的内存管理方式。因此切换语言后还是要注意内存管理的不同。

Q1:把一个 weak 指针变量当做参数传给一个方法,当方法开始调用时,即使对象被其他线程释放了,此刻对象也不会被销毁,原因是什么?是形参这个指针变量又强引用了对象,导致对象不会被销毁,还是因为编译器插入的objc_loadWeakRetained函数?还是说两者都有?

两者都有,因为函数调用时会有一些压栈操作,如果没有objc_loadWeakRetained,那么在压栈操作期间,对象可能就销毁了。另外形参赋值的时候编译器也会插入objc_storeStrong这时形参指向的对象会被 retain ,当函数执行完成时,形参被废弃会调用release 方法。函数返回后继续执行objc_release(tmp);,释放对象。

编译器对方法的入参对象进行retain和release:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testOjcParamCompilerInsertRetain];
}

- (void)testOjcParamCompilerInsertRetain {
XQModel *obj = [XQModel new];
[self hello:obj];
}

- (void)hello:(XQModel *)model {
NSLog(@"model:%@", model);
}

NSLog(@"model:%@", model); 处打好断点,如下图:

可以看到编译器会插入objc_storeStrong,从而对传入的 model 对象进行 retain,保证参数指向的对象在使用期间不被销毁。

可以po一下model的引用计数:已经变为2了。

1
2
(lldb) po NSLog(@"retain count = %ld\n", CFGetRetainCount((__bridge  CFTypeRef)(model)));
2020-09-14 17:18:20.496168+0800 iOSWeakDemo[53997:4829011] retain count = 2

当方法执行完后,又会执行objc_storeStrong(&model, nil); 释放对象。

这个其实在MRC下会经常写这样的代码,对入参对象retain,使用完后release。只不过在ARC下编译器帮你自动完成了这些操作。

Q2:函数的调用过程究竟是怎样的?

参考:

C函数调用过程原理及函数栈帧分析

X86-64寄存器和栈帧

Q3:在 OC 中 [obj hello]; 实际上是会变成objc_msgSend(obj, "hello")的调用,-(void)hello;方法其实是暗含了两个参数的一个是 id self,一个是 sel _cmd,问 self 是否会强引用对象?

从观察到的现象来看是没有的,举例:

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
33
34
35
36
37
38
39
40
41
- (void)viewDidLoad {
[super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testObjLife];
}

#pragma mark- weak
- (void)testObjLife {
self.obj = [ARCPerson new];
NSLog(@"self.obj:%@", _obj);

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
self.obj = nil; //释放对象
NSLog(@"清除");
});
// [_obj doTask]; //崩溃。
/**
使用[self.obj doTask];
不会野指针崩溃,因为汇编代码里执行self.obj后会,接着调用的是objc_retainAutoreleasedReturnValue
*/
//这里其实是两个方法的调用:[[self obj] doTask];
//相当于ARCPerson *temp = [self obj]; //编译器会插入objc_retainAutoreleasedReturnValue。
//[temp doTask];
//temp超出作用域废弃时编译器插入objc_release,对应objc_retainAutoreleasedReturnValue。
[self.obj doTask];
}

//ARCPerson
- (void)doTask {
NSLog(@"开始计算");
float f = 0.0;
for (NSInteger i = 0; i < 60000000; i ++)
{
f = f + sin(sin(sin(time(NULL) + i )));
}
NSLog(@"计算完成");
NSLog(@"model:%@ rs:%ld", self, (long)f);
}

当执行了self.obj = nil; //释放对象后,对象就销毁了,然后崩溃在doTask里。说明 self 并没有强引用对象。

为啥使用 [self.obj doTask]; 就不会崩溃呢?

[self.obj doTask]; 处打好断点:

会发现 [self.obj doTask]; 等价于:

1
2
3
4
id tmp = objc_msgSend(self, "obj"); 
objc_retainAutoreleasedReturnValue(tmp); //貌似只要前一句代码是非常规获得的对象都会有这个函数的出现。
objc_msgSend(tmp, "doTask");
objc_release(tmp); //这里是objc_release而不是objc_storeStrong是因为我们的代码不是"id obj = [self obj]; [obj doTask];"

单步执行,并进入到objc_retainAutoreleasedReturnValue函数里:

会发现objc_retainAutoreleasedReturnValue的参数就是tmp,继续跟踪的话会发现objc_retainAutoreleasedReturnValue内部调用了objc_retain,于是tmp的引用计数+1,所以使用 [self.obj doTask]; 不会崩溃。

而直接使用示例变量 [_obj doTask];

则没有类似的objc_retainAutoreleasedReturnValue处理,因此2秒后当self.obj = nil;置为nil时,obj就会释放并销毁,doTask内部再访问self时会野指针崩溃。

打印日志:

1
2
3
4
5
6
2020-09-14 15:45:43.518910+0800 iOSWeakDemo[52902:4783450] self.obj:<ARCPerson: 0x6000039dc500>
2020-09-14 15:45:43.519025+0800 iOSWeakDemo[52902:4783450] 开始计算
2020-09-14 15:45:45.521771+0800 iOSWeakDemo[52902:4783607] ARCPerson dealloc
2020-09-14 15:45:45.521966+0800 iOSWeakDemo[52902:4783607] (null) 2
2020-09-14 15:45:45.522120+0800 iOSWeakDemo[52902:4783607] 清除
2020-09-14 15:45:59.732099+0800 iOSWeakDemo[52902:4783450] 计算完成

可以看到还没计算完成,self指向的对象就已经销毁了。

Q4:MRC下dealloc重写时super调用的位置?[super dealloc]到底干了啥?

1
2
3
4
5
6
7
8
9
10
11
12
- (void)dealloc {
NSLog(@"MRCPerson dealloc");
NSLog(@"%@ 4", self.cat);
[super dealloc]; //要放在最后
}

//崩溃写法
- (void)dealloc {
[super dealloc];
NSLog(@"MRCPerson dealloc");
NSLog(@"%@ 4", self.cat); //这里野指针崩溃。
}

dealloc方法就是释放对象自身的内存,以及析构它拥有的所有资源,比如实例变量对象。在MRC下,[super dealloc]必须放在最后。如果你放最开始的地方,那么执行完后对象就已经被销毁了,后面如果还有方法对象的地方将导致崩溃。

还是要看源码[super dealloc]。

参考:

Correct [super dealloc]

ARC下dealloc过程及.cxx_destruct的探究 4星

Q5:一个weak指针指向一个MRC对象,该MRC对象重写了dealloc但不调用[super dealloc],当该MRC对象销毁后,再打印该weak指针,会输出什么?nil还是对象地址?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];
[self test_subclass_do_not_call_super_dealloc];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"weakObj:%@", _wobj);//断点打在这
}

- (void)test_subclass_do_not_call_super_dealloc {
MRCPerson *per = [[MRCPerson alloc] init];
_wobj = per;
NSLog(@"_wobj:%@", _wobj);
}

//MRCPerson不调用[super dealloc]
- (void)dealloc {
NSLog(@"%@ dealloc", self.class);
// [super dealloc];
}

答案:输出nil。

分析:对象销毁时如果不调用[super dealloc],理论上不会清除对象的weak表,自然weak指针也不会被置为nil,因此应该输出对象的地址,然而却输出了nil,这说明某个地方做了一些处理。从上面的分析我们已经知道使用weak指针时,编译器会在前面插入 objc_loadWeakRetained ,使用完后在后面插入 objc_release

因此 NSLog(@"weakObj:%@", _wobj);//断点打在这 等价于:

1
2
3
id tmp = objc_loadWeakRetained(&_wobj);  
NSLog(@"weakObj:%@", tmp);
objc_release(tmp);

而 objc_loadWeakRetained 源码里会rootTryRetain:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;

SideTable *table;

retry:
// fixme std::atomic this load
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;

table = &SideTables()[obj];

table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}

result = obj;

cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) { //这里rootTryRetain
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, @selector(retainWeakReference));
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) { //retainWeakReference
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}

table->unlock();
return result;
}

rootTryRetain里会判断如果对象正在销毁,则返回nil,因此导致objc_loadWeakRetained也返回nil。于是tmp等于nil,自然打印的也是nil了。

我们可以重写retainWeakReference,返回yes,再运行一下,这时打印的就是对象的地址了。所以这里的nil并不是因为weak被清,weak指针被置为nil,而是因为objc_loadWeakRetained返回了nil。另外对于MRC来说重写dealloc方法一定要调用super的dealloc,否则销毁工作并没有完全结束,内存会有泄漏。

Q6:如何理解OC中内存管理相关的方法命名规则?

在OC中对于以alloc/init/new/copy/mutableCopy开头的方法创建返回的对象,是自己拥有的对象需要自己释放。其他开头的方法创建返回的对象,则不是自己拥有的对象不能自己释放,一般而言这样的对象是注册到自动释放池的对象,会在自动释放池销毁时释放,当然在一些特定情况下系统会优化性能避免注册到自动释放池,这一切对于开发者而言是无感知的,即使如此你依然没有拥有该对象不能自己释放。

比如myPerson方法:

1
2
3
4
+ (instancetype)myPerson {
ARCPerson *p = [[ARCPerson alloc] init];
return p;
}

return p; 打好断点,如下图:

在返回时系统会将对象注册到自动释放池里。因此自己不拥有该对象,也就不能调用release方法。

而对于newPerson方法:

1
2
3
4
+ (instancetype)newPerson {
ARCPerson *p = [[ARCPerson alloc] init];
return p;
}

如下图:

没有其他处理,返回的对象是自己拥有的,因此需要自己释放。

再比如getter方法:

1
2
3
4
5
@property (nonatomic, weak) MRCPerson *wobj;

- (MRCPerson *)wobj {
return _wobj;
}

对应的汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
iOSWeakDemo`-[ViewController wobj]:
0x101e945e0 <+0>: pushq %rbp
0x101e945e1 <+1>: movq %rsp, %rbp
0x101e945e4 <+4>: subq $0x10, %rsp
0x101e945e8 <+8>: movq %rdi, -0x8(%rbp)
0x101e945ec <+12>: movq %rsi, -0x10(%rbp)
0x101e945f0 <+16>: movq -0x8(%rbp), %rax
-> 0x101e945f4 <+20>: movq 0x968d(%rip), %rcx ; ViewController._wobj
0x101e945fb <+27>: addq %rcx, %rax
0x101e945fe <+30>: movq %rax, %rdi
0x101e94601 <+33>: callq 0x101e96024 ; symbol stub for: objc_loadWeakRetained
0x101e94606 <+38>: movq %rax, %rdi
0x101e94609 <+41>: addq $0x10, %rsp
0x101e9460d <+45>: popq %rbp
0x101e9460e <+46>: jmp 0x101e96006 ; symbol stub for: objc_autoreleaseReturnValue

因为这里是weak指针,所以这里调用的objc_loadWeakRetained函数。另外还会调用objc_autoreleaseReturnValue注册到自动释放池if needed。

如果是strong属性:

1
2
3
4
5
6
7
8
9
10
iOSWeakDemo`-[ViewController wobj]:
0x10529c5f0 <+0>: pushq %rbp
0x10529c5f1 <+1>: movq %rsp, %rbp
0x10529c5f4 <+4>: movq %rdi, -0x8(%rbp)
0x10529c5f8 <+8>: movq %rsi, -0x10(%rbp)
0x10529c5fc <+12>: movq -0x8(%rbp), %rax
-> 0x10529c600 <+16>: movq 0x9689(%rip), %rcx ; ViewController._wobj
0x10529c607 <+23>: movq (%rax,%rcx), %rdi
0x10529c60b <+27>: popq %rbp
0x10529c60c <+28>: jmp 0x10529e02c ; symbol stub for: objc_retainAutoreleaseReturnValue

就只有objc_retainAutoreleaseReturnValue了。该函数的作用就是retain+autorelease。

觉得文章有帮助可以打赏一下哦!