__weak與__block區別,深層理解兩者區別
轉載於:https://www.cnblogs.com/yajunLi/p/6203222.html?utm_source=itdadao&utm_medium=referral
準備工作
首先我定義了一個類 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 = @"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
是可以避免迴圈引用問題的
接下來我們再看第二段程式碼
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 的原始碼,會發現 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);
變數記憶體地址: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 來保證程式碼執行的先後順序。
從結果中我們可以看到,只要 block 部分執行了,即使我們中途釋放了 obj,block 內部依然會繼續強引用它。對比上面程式碼,也就是說 block 內部的 __strong 會在執行期間進行強引用操作,保證在 block 內部 strongObj 始終是可用的。這種寫法非常巧妙,既避免了迴圈引用的問題,又可以在 block 內部持有該變數。
綜合兩部分程式碼,我們平時在使用時,常常先判斷 strongObj 是否為空,然後再執行後續程式碼,如下方式:
這種方式先判斷 Obj 是否被釋放,如果未釋放在執行我們的程式碼的時候保證其可用性。
__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
能不能避免迴圈引用的問題
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 底部釋放掉 block 變數,這其實跟 MRC 的形式有些類似了,不太適合 ARC這種形式既能保證在 block 內部能夠訪問到 obj,又可以避免迴圈引用的問題,但是這種方法也不是完美的,其存在下面幾個問題
- 當在 block 外部修改了 blockObj 時,block 內部的值也會改變,反之在 block 內部修改 blockObj 在外部再使用時值也會改變。這就需要在寫程式碼時注意這個特性可能會帶來的一些隱患
-
__block
其實提升了變數的作用域,在 block 內外訪問的都是同一個 blockObj 可能會造成一些隱患
總結!!!
__weak
本身是可以避免迴圈引用的問題的,但是其會導致外部物件釋放了之後,block 內部也訪問不到這個物件的問題,我們可以通過在 block 內部宣告一個 __strong
的變數來指向 weakObj,使外部物件既能在 block 內部保持住,又能避免迴圈引用的問題。
__block
本身無法避免迴圈引用的問題,但是我們可以通過在 block 內部手動把 blockObj 賦值為 nil 的方式來避免迴圈引用的問題。另外一點就是 __block
修飾的變數在 block 內外都是唯一的,要注意這個特性可能帶來的隱患。
但是__block
有一點:這只是限制在ARC環境下。在非arc下,__block是可以避免引用迴圈的
相關文章
- HashMap底層實現原理/HashMap與HashTable區別/HashMap與HashSet區別HashMap
- Block型別及儲存區域BloC型別
- 小程式直播與抖音直播兩者有何區別?
- Linux中程式與程式分別指什麼?兩者的區別有哪些?Linux
- python 深/淺複製及其區別Python
- MyISAM與InnoDB兩者的區別、詳細總結、效能對比
- 簡單瞭解下linux與windows兩者的區別-行雲管家LinuxWindows
- ??與?:的區別
- 理解cookie、session、localStorage、sessionStorage的關係與區別CookieSession
- IL角度理解for 與foreach的區別——迭代器模式模式
- inline、block、inline-block這三個屬性值有什麼區別?inlineBloC
- mouseenter與mouseover區別
- currentTarget與target區別
- mouseout與mouseleave區別
- classList與className區別
- innerText與textContent區別
- GET與POST區別
- let與const區別
- NIO與IO區別
- 區別mouseover與mouseenter?
- ApplicationContext 與 BeanFactory 區別APPContextBean
- setInterval()與setTimeout()區別
- match()與exec()區別
- localStorage與sessionStorage 區別Session
- showModal()與show() 區別
- <section>與<article> 區別
- onmouseover與onmouseenter區別
- offset與style區別
- cellpadding與cellspacing 區別padding
- animation與transition 區別
- encodeURI與encodeURIComponent區別
- JavaScript與ECMAScript 區別JavaScript
- FragmentPagerAdapter與FragmentStatePagerAdapter區別FragmentAPT
- put與putIfAbsent區別
- JavaScript 與TypeScript區別JavaScriptTypeScript
- 四七層負載均衡的區別負載
- 值型別與引用型別的區別型別
- 積極者與消極者的15點區別