抛出问题
群友(@止一)昨晚上发了一个问题:
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
时场景如下:
当不打开的时候是这样的:
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); } }
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);
, 再看下这个是啥:
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
, 来试验一下:
GG, 我们注意到__destructor_8_s0
函数在test
中也出现过, 作用是释放@[@1, @2, @3]
临时变量, 难道作用是将ObjC对象交给C处理之后清理ObjC对象临时引用
? 按照这个思路我们应该试一下__bridge_retained
, 目的是伪造一个从Objc对象移交给CoreFounction
的假象,试一下:
不崩溃了~~
看下伪代码:
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"); }
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~