原文鏈接:http://honglu.me/2015/01/06/weak與block區(qū)別/
結(jié)論
__weak
本身是可以避免循環(huán)引用的問(wèn)題的而芥,但是其會(huì)導(dǎo)致外部對(duì)象釋放了之后紊搪,block 內(nèi)部也訪問(wèn)不到這個(gè)對(duì)象的問(wèn)題,我們可以通過(guò)在 block 內(nèi)部聲明一個(gè)__strong
的變量來(lái)指向 weakObj勤婚,使外部對(duì)象既能在 block 內(nèi)部保持住呛伴,又能避免循環(huán)引用的問(wèn)題褥民。
__block
本身無(wú)法避免循環(huán)引用的問(wèn)題季春,但是我們可以通過(guò)在 block 內(nèi)部手動(dòng)把 blockObj 賦值為 nil 的方式來(lái)避免循環(huán)引用的問(wèn)題。另外一點(diǎn)就是__block
修飾的變量在 block 內(nèi)外都是唯一的轴捎,要注意這個(gè)特性可能帶來(lái)的隱患鹤盒。
準(zhǔn)備工作
首先我定義了一個(gè)類 MyObject
繼承 NSObject
,并添加了一個(gè)屬性 text侦副,重寫(xiě)了description
方法侦锯,返回 text 的值。這個(gè)主要是因?yàn)榫幾g器本身對(duì) NSString 是有優(yōu)化的秦驯,創(chuàng)建的 string 對(duì)象有可能是靜態(tài)存儲(chǔ)區(qū)永不釋放的尺碰,為了避免使用 NSString 引起一些問(wèn)題,還是創(chuàng)建一個(gè) NSObject 對(duì)象比較合適译隘。
另外我自定義了一個(gè) TLog 方法輸出對(duì)象相關(guān)值亲桥,定義如下:
#define TLog(prefix,Obj) {NSLog(@"變量?jī)?nèi)存地址:%p, 變量值:%p, 指向?qū)ο笾担?@, --> %@",&Obj,Obj,Obj,prefix);}
__weak
我們測(cè)試下面一段代碼
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();
輸出:
變量?jī)?nèi)存地址:0x7fff58c8a9f0, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担簃y-object, --> obj
變量?jī)?nèi)存地址:0x7fff58c8a9e8, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担簃y-object, --> weakObj
變量?jī)?nèi)存地址:0x7f8e030804c0, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担簃y-object, --> weakObj - block
變量?jī)?nèi)存地址:0x7f8e030804c0, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block
從上面的結(jié)果可以看到
- block 內(nèi)的 weakObj 和外部的 weakObj 并不是同一個(gè)變量
- block 捕獲了 weakObj 同時(shí)也是對(duì) obj 進(jìn)行了弱引用,當(dāng)我在 block 外把 obj 釋放了之后固耘,block 內(nèi)也讀不到這個(gè)變量了
- 當(dāng) obj 賦值 nil 時(shí)题篷,block 內(nèi)部的 weakObj 也為 nil 了,也就是說(shuō) obj 實(shí)際上是被釋放了厅目,可見(jiàn) __weak 是可以避免循環(huán)引用問(wèn)題的
接下來(lái)我們?cè)倏吹诙未a
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);
輸出
變量?jī)?nèi)存地址:0x7fff5d7b2d18, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> obj
變量?jī)?nèi)存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> weakObj-0
變量?jī)?nèi)存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> weakObj-1
變量?jī)?nèi)存地址:0x7fcf78f0f520, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> weakObj - block
變量?jī)?nèi)存地址:0x7fff5d7b2bb8, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> strongObj - block
變量?jī)?nèi)存地址:0x7fff5d7b2d10, 變量值:0x7fcf78c11e80, 指向?qū)ο笾担簃y-object, --> weakObj-2
變量?jī)?nèi)存地址:0x7fcf78f0f520, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block
變量?jī)?nèi)存地址:0x7fff5d7b2bb8, 變量值:0x0, 指向?qū)ο笾担?null), --> strongObj - block
變量?jī)?nèi)存地址:0x7fff5d7b2d10, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-3
如果你看過(guò) AFNetworking 的源碼番枚,會(huì)發(fā)現(xiàn) AFN 中作者會(huì)把變量在 block 外面先用 __weak
聲明,在 block 內(nèi)把前面 weak 聲明的變量賦值給 __strong
修飾的變量這種寫(xiě)法损敷。
從上面例子我們看到即使在 block 內(nèi)部用 strong 強(qiáng)引用了外面的 weakObj 葫笼,但是一旦 obj 釋放了之后,內(nèi)部的 strongObj 同樣會(huì)變成 nil拗馒,那么這種寫(xiě)法又有什么意義呢路星?
下面再看一段代碼:
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í)行結(jié)果:
變量?jī)?nèi)存地址:0x7fff58e2ad18, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> obj
變量?jī)?nèi)存地址:0x7fff58e2ad10, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> weakObj-0
變量?jī)?nèi)存地址:0x7fa2b1e80710, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> weakObj - block
變量?jī)?nèi)存地址:0x700000093de8, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> strongObj - block
------ sleep 1s
變量?jī)?nèi)存地址:0x7fff58e2ad10, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> weakObj-1
------ sleep 5s
變量?jī)?nèi)存地址:0x7fa2b1e80710, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> weakObj - block
變量?jī)?nèi)存地址:0x700000093de8, 變量值:0x7fa2b1e804e0, 指向?qū)ο笾担簃y-object, --> strongObj - block
變量?jī)?nèi)存地址:0x7fff58e2ad10, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-2
代碼中使用 sleep 來(lái)保證代碼執(zhí)行的先后順序。從結(jié)果中我們可以看到诱桂,只要 block 部分執(zhí)行了洋丐,即使我們中途釋放了 obj,block 內(nèi)部依然會(huì)繼續(xù)強(qiáng)引用它挥等。對(duì)比上面代碼垫挨,也就是說(shuō) block 內(nèi)部的 __strong 會(huì)在執(zhí)行期間進(jìn)行強(qiáng)引用操作,保證在 block 內(nèi)部 strongObj 始終是可用的触菜。這種寫(xiě)法非常巧妙,既避免了循環(huán)引用的問(wèn)題哀峻,又可以在 block 內(nèi)部持有該變量涡相。
綜合兩部分代碼哲泊,我們平時(shí)在使用時(shí),常常先判斷 strongObj 是否為空催蝗,然后再執(zhí)行后續(xù)代碼切威,如下方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyObject *strongObj = weakObj;
if (strongObj) {
// do something ...
}
});
這種方式先判斷 Obj 是否被釋放,如果未釋放在執(zhí)行我們的代碼的時(shí)候保證其可用性丙号。
__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);
結(jié)果
變量?jī)?nèi)存地址:0x7fff5021a9f0, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担簃y-object-1, --> obj
變量?jī)?nèi)存地址:0x7fff5021a9e8, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担簃y-object-1, --> blockObj -1
<__NSMallocBlock__: 0x7ff6b48d8c20>
變量?jī)?nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担簃y-object-1, --> blockObj -2
變量?jī)?nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担簃y-object-1, --> blockObj - block
變量?jī)?nèi)存地址:0x7fff5021a7f8, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担簃y-object-2, --> obj2
變量?jī)?nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担簃y-object-2, --> blockObj - block
變量?jī)?nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担簃y-object-2, --> blockObj -3
可以看到在 block 聲明前后 blockObj 的內(nèi)存地址是有所變化的先朦,這涉及到 block 對(duì)外部變量的內(nèi)存管理問(wèn)題,大家可以看擴(kuò)展閱讀中的幾篇文章犬缨,對(duì)此有較深入的分析喳魏。
下面來(lái)看看 __block
能不能避免循環(huán)引用的問(wè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);
輸出
變量?jī)?nèi)存地址:0x7fff57eef9f0, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, --> obj
變量?jī)?nèi)存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, --> blockObj - block
變量?jī)?nèi)存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, --> blockObj
當(dāng)外部 obj 指向 nil 的時(shí)候,obj 理應(yīng)被釋放怀薛,但實(shí)際上 blockObj 依然強(qiáng)引用著 obj刺彩,obj 其實(shí)并沒(méi)有被真正釋放。因此使用 __block
并不能避免循環(huán)引用的問(wèn)題枝恋。
但是我們可以通過(guò)手動(dòng)釋放 blockObj 的方式來(lái)釋放 obj创倔,這就需要我們?cè)?block 內(nèi)部將要退出的時(shí)候手動(dòng)釋放掉 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 內(nèi)部能夠訪問(wèn)到 obj焚碌,又可以避免循環(huán)引用的問(wèn)題畦攘,但是這種方法也不是完美的,其存在下面幾個(gè)問(wèn)題
- 必須記住在 block 底部釋放掉 block 變量十电,這其實(shí)跟 MRC 的形式有些類似了知押,不太適合 ARC
- 當(dāng)在 block 外部修改了 blockObj 時(shí),block 內(nèi)部的值也會(huì)改變摆出,反之在 block 內(nèi)部修改 blockObj 在外部再使用時(shí)值也會(huì)改變朗徊。這就需要在寫(xiě)代碼時(shí)注意這個(gè)特性可能會(huì)帶來(lái)的一些隱患
-
__block
其實(shí)提升了變量的作用域,在 block 內(nèi)外訪問(wèn)的都是同一個(gè) blockObj 可能會(huì)造成一些隱患
擴(kuò)展閱讀
block使用小結(jié)偎漫、在arc中使用block爷恳、如何防止循環(huán)引用
Block教程系列
Block