iOS的__bridge_retained可以做什么

抛出问题

群友(@止一)昨晚上发了一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct Test {
NSArray *arr;
} Test;
- (void)test {
Test test = {};
test.arr = @[@1, @2, @3];
self.var = test;
}

- (void)viewDidLoad {
[super viewDidLoad];
[self test];
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", self.var.arr);
}

}

xcode12这样写会崩溃

大佬提供的一些资料

还原一下场景

打开Zombie Objects时场景如下:

Xcode12-Zombie-Objects

当不打开的时候是这样的:

Xcode12-No-Zombie-Objects

Xcode11并不崩溃:

Xcode11

对比一下汇编伪代码

仔细对比了一下代码,发现test函数完全一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)test {
Test test = {};
test.arr = @[@1, @2, @3];
self.var = test;
}
// 汇编伪代码
void __cdecl -[XX test](XX *self, SEL a2)
{
v14 = self;
v13 = a2;
v8 = @[@1, @2, @3];
v12 = objc_retainAutoreleasedReturnValue(v8);
objc_release(0LL);
__copy_constructor_8_8_s0(&v11, &v12);
v9 = v14;
__copy_constructor_8_8_s0(&v10, &v11);
-[XX setVar:](v9, "setVar:", v10);
__destructor_8_s0(&v12);
}

然后, 关联的for-NSLog:

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
- (void)main {
[self test];
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", self.var.arr);
}
}
// Xcode12下汇编伪代码
void __cdecl -[XX main](XX *self, SEL a2)
{
void *v2; // [rsp+10h] [rbp-20h]
int i; // [rsp+1Ch] [rbp-14h]
SEL v4; // [rsp+20h] [rbp-10h]
void *v5; // [rsp+28h] [rbp-8h]

v5 = self;
v4 = a2;
-[XX test](self, "test");
for ( i = 0; i < 1000; ++i )
{
v2 = objc_msgSend(v5, "var");
NSLog(CFSTR("%@"), v2);
__destructor_8_s0((__int64)&v2);
}
}
// Xcode11下汇编伪代码
void __cdecl -[XX main](XX *self, SEL a2)
{
Test v2; // ST10_8
signed int i; // [rsp+1Ch] [rbp-14h]

-[XX test](self, "test");
for ( i = 0; i < 1000; ++i )
{
v2.var0 = -[XX var](self, "var").var0;
NSLog(CFSTR("%@"), v2.var0);
}
}

所以Xcode12比Xcode11多了一行__destructor_8_s0((__int64)&v2);, 再看下这个是啥:

1
2
3
4
__int64 __fastcall __destructor_8_s0(__int64 a1)
{
return objc_storeStrong(a1, 0LL);
}

结案, Xcode12下self.var.arr之后会将返回值进行一次release

Xcode12的这种情境下如何修正

按照iOS的__bridge、__bridge_transfer究竟做了啥的思路来: 我们需要加一下__bridge*的标记, struct虽然不是CoreFounction的东西,但是可以用的

从对比来看, 我们应该修改的是for-NSLog中的代码, 这样才不至于引出其他的问题, 然后从上文可以得知, 此处我们应该用__bridge, 来试验一下:

Xcode12-__bridge

GG, 我们注意到__destructor_8_s0函数在test中也出现过, 作用是释放@[@1, @2, @3]临时变量, 难道作用是将ObjC对象交给C处理之后清理ObjC对象临时引用? 按照这个思路我们应该试一下__bridge_retained, 目的是伪造一个从Objc对象移交给CoreFounction的假象,试一下:

Xcode12-__bridge_retained

不崩溃了~~

看下伪代码:

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
- (void)main {
[self test];
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", (__bridge_retained void*)(self.var.arr));
}
printf("over\n");
}
// Xcode12伪代码
void __cdecl -[XX main](XX *self, SEL a2)
{
void *v2; // rax
__int64 v3; // rax
void *v4; // [rsp+10h] [rbp-20h]
int i; // [rsp+1Ch] [rbp-14h]
SEL v6; // [rsp+20h] [rbp-10h]
void *v7; // [rsp+28h] [rbp-8h]

v7 = self;
v6 = a2;
-[XX test](self, "test");
for ( i = 0; i < 1000; ++i )
{
v2 = objc_msgSend(v7, "var");
v4 = v2;
v3 = objc_retain(v2);
NSLog(CFSTR("%@"), v3);
__destructor_8_s0((__int64)&v4);
}
}

最后

__bridge__bridge_transfer__bridge_retained, 而没有一处是关于CoreFounction的东西, 却看到了ARC下对引用计数的干预…

愿世界再无BUG~