抛出问题
群友(@止一)昨晚上发了一个问题:
| 12
 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时场景如下:

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

Xcode11并不崩溃:

对比一下汇编伪代码
仔细对比了一下代码,发现test函数完全一样:
| 12
 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:
| 12
 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);
 }
 }
 
 void __cdecl -[XX main](XX *self, SEL a2)
 {
 void *v2;
 int i;
 SEL v4;
 void *v5;
 
 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);
 }
 }
 
 void __cdecl -[XX main](XX *self, SEL a2)
 {
 Test v2;
 signed int i;
 
 -[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);, 再看下这个是啥:
| 12
 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, 来试验一下:

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

不崩溃了~~
看下伪代码:
| 12
 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");
 }
 
 void __cdecl -[XX main](XX *self, SEL a2)
 {
 void *v2;
 __int64 v3;
 void *v4;
 int i;
 SEL v6;
 void *v7;
 
 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~