天天看點

__weak和__block探究與差別

準備工作

首先我定義了一個類 

MyObject

 繼承 

NSObject

,并添加了一個屬性 text,重寫了

description

方法,傳回 text 的值。這個主要是因為編譯器本身對 NSString 是有優化的,建立的 string 對象有可能是靜态存儲區永不釋放的,為了避免使用 NSString 引起一些問題,還是建立一個 NSObject 對象比較合适。

另外我自定義了一個 TLog 方法輸出對象相關值,定義如下:

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

__weak

我們測試下面一段代碼

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

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

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

輸出:

變量記憶體位址:0x7fff58c8a9f0, 變量值:0x7f8e0307f1d0, 指向對象值:111111111111, --> obj
變量記憶體位址:0x7fff58c8a9e8, 變量值:0x7f8e0307f1d0, 指向對象值:111111111111, --> weakObj
變量記憶體位址:0x7f8e030804c0, 變量值:0x7f8e0307f1d0, 指向對象值:111111111111, --> weakObj - block
變量記憶體位址:0x7f8e030804c0, 變量值:0x0, 指向對象值:(null), --> weakObj - block
      

從上面的結果可以看到

  • block 内的 weakObj 和外部的 weakObj 并不是同一個變量
  • block 捕獲了 weakObj 同時也是對 obj 進行了弱引用,當我在 block 外把 obj 釋放了之後,block 内也讀不到這個變量了
  • 當 obj 指派 nil 時,block 内部的 weakObj 也為 nil 了,也就是說 obj 實際上是被釋放了,可見 

    __weak

     是可以避免循環引用問題的

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

MyObject *obj = [[MyObject alloc]init];
obj.text = @"111111111111";
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;
    sleep(3);
    TLog(@"weakObj - block", weakObj);
    TLog(@"strongObj - block", strongObj);
});
sleep(1);
obj = nil;
TLog(@"weakObj-1", weakObj);
sleep(4);
TLog(@"weakObj-2", weakObj);
           

輸出

變量記憶體位址:0x7fff572a9ab8, 變量值:0x7f9562f76210, 指向對象值:111111111111, --> obj
變量記憶體位址:0x7fff572a9ab0, 變量值:0x7f9562f76210, 指向對象值:111111111111, --> weakObj-0
變量記憶體位址:0x7fff572a9ab0, 變量值:0x7f9562f76210, 指向對象值:111111111111, --> weakObj-1
變量記憶體位址:0x7f9562d143a0, 變量值:0x7f9562f76210, 指向對象值:111111111111, --> weakObj - block
變量記憶體位址:0x116ccbe08, 變量值:0x7f9562f76210, 指向對象值:111111111111, --> strongObj - block
變量記憶體位址:0x7fff572a9ab0, 變量值:0x0, 指向對象值:(null), --> weakObj-2
      

如果你看過 AFNetworking 的源碼,會發現 AFN 中作者會把變量在 block 外面先用 

__weak

 聲明,在 block 内把前面 weak 聲明的變量指派給 

__strong

 修飾的變量。這種寫法的好處就是可以讓變量在 block 内部安全可用,即使外部釋放了,也會在 block 的生命周期内保留該變量。這種寫法非常巧妙,既避免了循環引用的問題,又可以在 block 内部持有該變量。

__block

先上代碼

MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
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 = @"222222222222";
    TLog(@"obj2",obj2);
    blockObj = obj2;
    TLog(@"blockObj - block",blockObj);
};
NSLog(@"%@",testBlock);
TLog(@"blockObj -2",blockObj);
testBlock();
TLog(@"blockObj -3",blockObj);
           

結果

變量記憶體位址:0x7fff5021a9f0, 變量值:0x7ff6b48d8cd0, 指向對象值:11111111111111, --> obj
變量記憶體位址:0x7fff5021a9e8, 變量值:0x7ff6b48d8cd0, 指向對象值:11111111111111, --> blockObj -1
<__NSMallocBlock__: 0x7ff6b48d8c20>
變量記憶體位址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向對象值:11111111111111, --> blockObj -2
變量記憶體位址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向對象值:11111111111111, --> blockObj - block
變量記憶體位址:0x7fff5021a7f8, 變量值:0x7ff6b48d9960, 指向對象值:222222222222, --> obj2
變量記憶體位址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向對象值:222222222222, --> blockObj - block
變量記憶體位址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向對象值:222222222222, --> blockObj -3
      

我對 

__block

 的了解是其實際上是把變量的作用域給改變了,應該是提升了變量的作用域,使得在 block 内部和外部所通路的是同一個變量。類似于聲明一個 static 和 global 變量的意思。

之是以代碼中列印了一個 testBlock 的類型,是因為可以看到在 testBlock 聲明前,blockObj 的位址和聲明後的位址是有變化的,也就是說 blockObj 應該是在 block 内部提升的作用域。

再來看看 

__block

 能不能避免循環引用的問題

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

 并不能避免循環引用的問題。

但是我們可以通過手動釋放 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,又可以避免循環引用的問題,但是這種方法也不是完美的,其存在下面幾個問題

  • 必須記住在 block 底部釋放掉 block 變量,這其實跟 MRC 的形式有些類似了,不太适合 ARC
  • 當在 block 外部修改了 blockObj 時,block 内部的值也會改變,反之在 block 内部修改 blockObj 在外部再使用時值也會改變。這就需要在寫代碼時注意這個特性可能會帶來的一些隐患
  • __block

     其實提升了變量的作用域,在 block 内外通路的都是同一個 blockObj 可能會造成一些隐患

總結

__weak

 本身是可以避免循環引用的問題的,但是其會導緻外部對象釋放了之後,block 内部也通路不到這個對象的問題,我們可以通過在 block 内部聲明一個 

__strong

 的變量來指向 weakObj,使外部對象既能在 block 内部保持住,又能避免循環引用的問題

__block

 本身無法避免循環引用的問題,但是我們可以通過在 block 内部手動把 blockObj 指派為 nil 的方式來避免循環引用的問題。另外一點就是 

__block

 修飾的變量在 block 内外都是唯一的,要注意這個特性可能帶來的隐患。

繼續閱讀