__weak與__block區(qū)別

原文鏈接:http://honglu.me/2015/01/06/weak與block區(qū)別/

結論

__weak 本身是可以避免循環(huán)引用的問題的,但是其會導致外部對象釋放了之后,block 內部也訪問不到這個對象的問題,我們可以通過在 block 內部聲明一個__strong 的變量來指向 weakObj,使外部對象既能在 block 內部保持住,又能避免循環(huán)引用的問題。

__block 本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是__block 修飾的變量在 block 內外都是唯一的,要注意這個特性可能帶來的隱患。

準備工作

首先我定義了一個類 MyObject 繼承 NSObject,并添加了一個屬性 text,重寫了description方法,返回 text 的值。這個主要是因為編譯器本身對 NSString 是有優(yōu)化的,創(chuàng)建的 string 對象有可能是靜態(tài)存儲區(qū)永不釋放的,為了避免使用 NSString 引起一些問題,還是創(chuàng)建一個 NSObject 對象比較合適。
另外我自定義了一個 TLog 方法輸出對象相關值,定義如下:

#define TLog(prefix,Obj) {NSLog(@"變量內存地址:%p, 變量值:%p, 指向對象值:%@, --> %@",&Obj,Obj,Obj,prefix);}

__weak

我們測試下面一段代碼

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
TLog(@"obj", obj);

__weak MyObject *weakObj = obj;
TLog(@"weakObj", weakObj);

void(^testBlock)() = ^(){
    TLog(@"weakObj - block", weakObj);
};
testBlock();
obj = nil;
testBlock();

輸出:

變量內存地址:0x7fff58c8a9f0, 變量值:0x7f8e0307f1d0, 指向對象值:my-object, --> obj
變量內存地址:0x7fff58c8a9e8, 變量值:0x7f8e0307f1d0, 指向對象值:my-object, --> weakObj
變量內存地址:0x7f8e030804c0, 變量值:0x7f8e0307f1d0, 指向對象值:my-object, --> weakObj - block
變量內存地址:0x7f8e030804c0, 變量值:0x0, 指向對象值:(null), --> weakObj - block

從上面的結果可以看到

  • block 內的 weakObj 和外部的 weakObj 并不是同一個變量
  • block 捕獲了 weakObj 同時也是對 obj 進行了弱引用,當我在 block 外把 obj 釋放了之后,block 內也讀不到這個變量了
  • 當 obj 賦值 nil 時,block 內部的 weakObj 也為 nil 了,也就是說 obj 實際上是被釋放了,可見 __weak 是可以避免循環(huán)引用問題的

接下來我們再看第二段代碼

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
TLog(@"obj", obj);
    
__weak MyObject *weakObj = obj;
TLog(@"weakObj-0", weakObj);
    
void(^testBlock)() = ^(){
   __strong MyObject *strongObj = weakObj;
   TLog(@"weakObj - block", weakObj);
   TLog(@"strongObj - block", strongObj);
};

TLog(@"weakObj-1", weakObj);
testBlock();
TLog(@"weakObj-2", weakObj);
obj = nil;
testBlock();
TLog(@"weakObj-3", weakObj);

輸出

變量內存地址:0x7fff5d7b2d18, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> obj
變量內存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> weakObj-0
變量內存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> weakObj-1
變量內存地址:0x7fcf78f0f520, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> weakObj - block
變量內存地址:0x7fff5d7b2bb8, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> strongObj - block
變量內存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向對象值:my-object, --> weakObj-2
變量內存地址:0x7fcf78f0f520, 變量值:0x0, 指向對象值:(null), --> weakObj - block
變量內存地址:0x7fff5d7b2bb8, 變量值:0x0, 指向對象值:(null), --> strongObj - block
變量內存地址:0x7fff5d7b2d10, 變量值:0x0, 指向對象值:(null), --> weakObj-3

如果你看過 AFNetworking 的源碼,會發(fā)現(xiàn) AFN 中作者會把變量在 block 外面先用 __weak 聲明,在 block 內把前面 weak 聲明的變量賦值給 __strong 修飾的變量這種寫法。

從上面例子我們看到即使在 block 內部用 strong 強引用了外面的 weakObj ,但是一旦 obj 釋放了之后,內部的 strongObj 同樣會變成 nil,那么這種寫法又有什么意義呢?

下面再看一段代碼:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
TLog(@"obj", obj);
    
__weak MyObject *weakObj = obj;
TLog(@"weakObj-0", weakObj);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   __strong MyObject *strongObj = weakObj;
   TLog(@"weakObj - block", weakObj);
   TLog(@"strongObj - block", strongObj);
   
   sleep(3);
   
   TLog(@"weakObj - block", weakObj);
   TLog(@"strongObj - block", strongObj);
});
NSLog(@"------ sleep 1s");
sleep(1);
obj = nil;
TLog(@"weakObj-1", weakObj);
NSLog(@"------ sleep 5s");
sleep(5);
TLog(@"weakObj-2", weakObj);

執(zhí)行結果:

變量內存地址:0x7fff58e2ad18, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> obj
變量內存地址:0x7fff58e2ad10, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> weakObj-0
變量內存地址:0x7fa2b1e80710, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> weakObj - block
變量內存地址:0x700000093de8, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> strongObj - block
------ sleep 1s
變量內存地址:0x7fff58e2ad10, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> weakObj-1
------ sleep 5s
變量內存地址:0x7fa2b1e80710, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> weakObj - block
變量內存地址:0x700000093de8, 變量值:0x7fa2b1e804e0, 指向對象值:my-object, --> strongObj - block
變量內存地址:0x7fff58e2ad10, 變量值:0x0, 指向對象值:(null), --> weakObj-2

代碼中使用 sleep 來保證代碼執(zhí)行的先后順序。從結果中我們可以看到,只要 block 部分執(zhí)行了,即使我們中途釋放了 obj,block 內部依然會繼續(xù)強引用它。對比上面代碼,也就是說 block 內部的 __strong 會在執(zhí)行期間進行強引用操作,保證在 block 內部 strongObj 始終是可用的。這種寫法非常巧妙,既避免了循環(huán)引用的問題,又可以在 block 內部持有該變量。

綜合兩部分代碼,我們平時在使用時,常常先判斷 strongObj 是否為空,然后再執(zhí)行后續(xù)代碼,如下方式:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   __strong MyObject *strongObj = weakObj;
   if (strongObj) {
       // do something ...
   }
});

這種方式先判斷 Obj 是否被釋放,如果未釋放在執(zhí)行我們的代碼的時候保證其可用性。

__block

先上代碼

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object-1";
TLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
TLog(@"blockObj -1",blockObj);

void(^testBlock)() = ^(){
    TLog(@"blockObj - block",blockObj);
    MyObject *obj2 = [[MyObject alloc]init];
    obj2.text = @"my-object-2";
    TLog(@"obj2",obj2);
    blockObj = obj2;
    TLog(@"blockObj - block",blockObj);
};
NSLog(@"%@",testBlock);
TLog(@"blockObj -2",blockObj);
testBlock();
TLog(@"blockObj -3",blockObj);

結果

變量內存地址:0x7fff5021a9f0, 變量值:0x7ff6b48d8cd0, 指向對象值:my-object-1, --> obj
變量內存地址:0x7fff5021a9e8, 變量值:0x7ff6b48d8cd0, 指向對象值:my-object-1, --> blockObj -1
<__NSMallocBlock__: 0x7ff6b48d8c20>
變量內存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向對象值:my-object-1, --> blockObj -2
變量內存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向對象值:my-object-1, --> blockObj - block
變量內存地址:0x7fff5021a7f8, 變量值:0x7ff6b48d9960, 指向對象值:my-object-2, --> obj2
變量內存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向對象值:my-object-2, --> blockObj - block
變量內存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向對象值:my-object-2, --> blockObj -3

可以看到在 block 聲明前后 blockObj 的內存地址是有所變化的,這涉及到 block 對外部變量的內存管理問題,大家可以看擴展閱讀中的幾篇文章,對此有較深入的分析。

下面來看看 __block 能不能避免循環(huán)引用的問題

MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
TLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)() = ^(){
    TLog(@"blockObj - block",blockObj);
};
obj = nil;
testBlock();
TLog(@"blockObj",blockObj);

輸出

變量內存地址:0x7fff57eef9f0, 變量值:0x7ff86a55a160, 指向對象值:11111111111111, --> obj
變量內存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向對象值:11111111111111, --> blockObj - block
變量內存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向對象值:11111111111111, --> blockObj

當外部 obj 指向 nil 的時候,obj 理應被釋放,但實際上 blockObj 依然強引用著 obj,obj 其實并沒有被真正釋放。因此使用 __block 并不能避免循環(huán)引用的問題。

但是我們可以通過手動釋放 blockObj 的方式來釋放 obj,這就需要我們在 block 內部將要退出的時候手動釋放掉 blockObj ,如下這種形式

MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
TLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)() = ^(){
    TLog(@"blockObj - block",blockObj);
    blockObj = nil;
};
obj = nil;
testBlock();
TLog(@"blockObj",blockObj);

這種形式既能保證在 block 內部能夠訪問到 obj,又可以避免循環(huán)引用的問題,但是這種方法也不是完美的,其存在下面幾個問題

  • 必須記住在 block 底部釋放掉 block 變量,這其實跟 MRC 的形式有些類似了,不太適合 ARC
  • 當在 block 外部修改了 blockObj 時,block 內部的值也會改變,反之在 block 內部修改 blockObj 在外部再使用時值也會改變。這就需要在寫代碼時注意這個特性可能會帶來的一些隱患
  • __block 其實提升了變量的作用域,在 block 內外訪問的都是同一個 blockObj 可能會造成一些隱患

擴展閱讀
block使用小結、在arc中使用block、如何防止循環(huán)引用
Block教程系列
Block

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容