1. 程式人生 > >Weak-Strong-Dance 真的安全嗎?

Weak-Strong-Dance 真的安全嗎?

絕大多數iOS開發者用過block,並且知道用 __weak 的方式去解決迴圈引用的問題。而進階一些的開發者則瞭解Weak-Strong-Dance,那麼什麼是Weak-Strong-Dance?它能保證block執行是的“安全”嗎?

1857952-d1a254bf05662388

Weak-Strong-Dance

看看下面兩段程式碼的區別,你就明白什麼是Weak-Strong-Dance了。

123456 -(void)test{__weak typeof(self)weakSelf=self;self.block=^{[weakSelf copy];};}
1234567 -(void)test{__weak typeof(self)weakSelf=self;self.block=^{__strong typeof(self)strongSelf=weakSelf;[strongSelf copy];};}

也就是在用 __weak 解決迴圈引用的前提下 ,在block內部用 __strong 持有物件,試圖解決“在多執行緒下,可能weakSelf指向的物件會在 Block 執行前被廢棄,導致各種各樣的問題,比如說KVO,傳入nil可是會crash呢”

,如下程式碼

123456 __weak typeof(self)weakSelf=self;self.handler=^{typeof(weakSelf)strongSelf=weakSelf;[strongSelf.obserable removeObserver:strongSelfforKeyPath:kObservableProperty];};

此時,你可能會這樣認為,self 所指向物件的引用計數變成 2,即使主執行緒中的 self 因為超出作用於而釋放,物件的引用計數依然為 1,避免了物件的銷燬。

思維糾正

它真的能解決在多執行緒下,可能 weakSelf 指向的物件會在 Block 執行前被廢棄而導致的問題嗎?
答案當然是否定的,讓我們來看看demo:

不用Weak-Strong-Dance

123456789101112131415161718 #import "TestBlock.h"@interfaceTestBlock()@property(nonatomic,strong)dispatch_block_t block;@end@implementation TestBlock-(void)test{__weak typeof(self)weakSelf=self;self.block=^{[weakSelf copy];};}@end

看看用clang改寫後的程式碼,這裡就只貼關鍵程式碼了:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546 // @interface TestBlock ()// @property (nonatomic, strong) dispatch_block_t block;/* @end */// @implementation TestBlockstruct__TestBlock__test_block_impl_0{struct__block_impl impl;struct__TestBlock__test_block_desc_0*Desc;TestBlock *const__weak weakSelf;__TestBlock__test_block_impl_0(void*fp,struct__TestBlock__test_block_desc_0 *desc,TestBlock *const__weak _weakSelf,intflags=0):weakSelf(_weakSelf){impl.isa=&_NSConcreteStackBlock;impl.Flags=flags;impl.FuncPtr=fp;Desc=desc;}};staticvoid__TestBlock__test_block_func_0(struct__TestBlock__test_block_impl_0 *__cself){TestBlock *const__weak weakSelf=__cself->weakSelf;// bound by copy((id(*)(id,SEL))(void*)objc_msgSend)((id)weakSelf,sel_registerName("copy"));}staticvoid__TestBlock__test_block_copy_0(struct__TestBlock__test_block_impl_0*dst,struct__TestBlock__test_block_impl_0*src){_Block_object_assign((void*)&dst->weakSelf,(void*)src->weakSelf,3/*BLOCK_FIELD_IS_OBJECT*/);}staticvoid__TestBlock__test_block_dispose_0(struct__TestBlock__test_block_impl_0*src){_Block_object_dispose((void*)src->weakSelf,3/*BLOCK_FIELD_IS_OBJECT*/);}staticstruct__TestBlock__test_block_desc_0{size_t reserved;size_t Block_size;void(*copy)(struct__TestBlock__test_block_impl_0*,struct__TestBlock__test_block_impl_0*);void(*dispose)(struct__TestBlock__test_block_impl_0*);}__TestBlock__test_block_desc_0_DATA={0,sizeof(struct__TestBlock__test_block_impl_0),__TestBlock__test_block_copy_0,__TestBlock__test_block_dispose_0};staticvoid_I_TestBlock_test(TestBlock *self,SEL _cmd){__attribute__((objc_ownership(weak)))typeof(self)weakSelf=self;((void(*)(id,SEL,dispatch_block_t))(void*)objc_msgSend)((id)self,sel_registerName("setBlock:"),((void(*)())&__TestBlock__test_block_impl_0((void*)__TestBlock__test_block_func_0,&__TestBlock__test_block_desc_0_DATA,weakSelf,570425344)));}staticvoid(*_I_TestBlock_block(TestBlock *self,SEL _cmd))(){return(*(__strong dispatch_block_t *)((char*)self+OBJC_IVAR_$_TestBlock$_block));}staticvoid_I_TestBlock_setBlock_(TestBlock *self,SEL _cmd,dispatch_block_t block){(*(__strong dispatch_block_t *)((char*)self+OBJC_IVAR_$_TestBlock$_block))=block;}// @end

程式碼很長,解釋下:
struct __TestBlock__test_block_impl_0裡頭,我們能看到TestBlock *const __weak weakSelf;這代表在 block 內部是以弱引用的方式捕獲 self 的,這沒毛病。重點來了,看這一段代表 block 具體實現的程式碼塊

12345 staticvoid__TestBlock__test_block_func_0(struct__TestBlock__test_block_impl_0 *__cself){TestBlock *const__weak weakSelf=__cself->weakSelf;// bound by copy((id(*)(id,SEL))(void*)objc_msgSend)((id)weakSelf,sel_registerName("copy"));}

這裡可以看到如果此時外部廢棄了self,的確會導致 block 內部訪問成nil的情況。

那麼如果用了Weak-Strong-Dance呢?

12345 __weak typeof(self)weakSelf=self;self.block=^{__strong typeof(self)strongSelf=weakSelf;[strongSelf copy];};

看看clang改寫後會有什麼區別:

1234567891011121314151617 struct__TestBlock__test_block_impl_0{struct__block_impl impl;struct__TestBlock__test_block_desc_0*Desc;TestBlock *const__weak weakSelf;__TestBlock__test_block_impl_0(void*fp,struct__TestBlock__test_block_desc_0 *desc,TestBlock *const__weak _weakSelf,intflags=0):weakSelf(_weakSelf){impl.isa=&_NSConcreteStackBlock;impl.Flags=flags;impl.FuncPtr=fp;Desc=desc;}};staticvoid__TestBlock__test_block_func_0(struct__TestBlock__test_block_impl_0 *__cself){TestBlock *const__weak weakSelf=__cself->weakSelf;// bound by copy__attribute__((objc_ownership(strong)))typeof(self)strongSelf=weakSelf;((id(*)(id,SEL))(void*)objc_msgSend)((id)strongSelf,sel_registerName("copy"));}

holy shit!

區別在於在 block 內多了這麼一行程式碼__attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;

所以持有 self 的行為是在 block 執行的時候才發生的!

回過頭來看看問題:它真的能解決在多執行緒下,可能 weakSelf 指向的物件會在 Block 執行前被廢棄而導致的問題嗎?

在執行前就廢棄,到了執行的時候,weakSelf 已經是 nil 了,此時執行 __strong typeof(self) strongSelf = weakSelf;根本沒意義吧。

所以在剛才KVO的例子中,該crash還是繼續crash吧。只要在執行__strong typeof(self) strongSelf = weakSelf;前,物件在其他執行緒被廢棄了,Weak-Strong-Dance不能幫上任何忙!

總結

Weak-Strong-Dance並不能保證 block所引用物件的釋放時機在執行之後, 更安全的做法應該是在 block 內部使用 strongSelf 時進行 nil檢測,這樣可以避免上述情況。