公司最近在招 iOS,我面試了幾個(gè)人,問到 block 避免循環(huán)引用的問題時(shí),發(fā)現(xiàn)好多人都說通過添加__block修飾詞來避免。再加上我對(duì)__block和__weak也沒有區(qū)分的太明確,搞得我都有點(diǎn)兒懷疑我自己以前是不是用錯(cuò)了。正好借這個(gè)機(jī)會(huì)來一探究竟~
準(zhǔn)備工作
首先我定義了一個(gè)類MyObject繼承NSObject,并添加了一個(gè)屬性 text,重寫了description方法,返回 text 的值。這個(gè)主要是因?yàn)榫幾g器本身對(duì) NSString 是有優(yōu)化的,創(chuàng)建的 string 對(duì)象有可能是靜態(tài)存儲(chǔ)區(qū)永不釋放的,為了避免使用 NSString 引起一些問題,還是創(chuàng)建一個(gè) NSObject 對(duì)象比較合適。
另外我自定義了一個(gè) TLog 方法輸出對(duì)象相關(guān)值,定義如下:
#define TLog(prefix,Obj) {NSLog(@"變量內(nèi)存地址:%p, 變量值:%p, 指向?qū)ο笾担?@, --> %@",&Obj,Obj,Obj,prefix);}
__weak
我們測試下面一段代碼
MyObject *obj = [[MyObject alloc]init];
obj.text=@"111111111111";
TLog(@"obj", obj);
__weakMyObject *weakObj = obj;
TLog(@"weakObj", weakObj);
void(^testBlock)() = ^(){
TLog(@"weakObj - block", weakObj);
};
testBlock();
obj =nil;
testBlock();
輸出:
變量內(nèi)存地址:0x7fff58c8a9f0, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担?11111111111, -->obj
變量內(nèi)存地址:0x7fff58c8a9e8, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担?11111111111, -->weakObj
變量內(nèi)存地址:0x7f8e030804c0, 變量值:0x7f8e0307f1d0, 指向?qū)ο笾担?11111111111, -->weakObj - block
變量內(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 了,也就是說 obj 實(shí)際上是被釋放了,可見__weak是可以避免循環(huán)引用問題的
接下來我們?cè)倏吹诙未a
MyObject *obj = [[MyObject alloc]init];
obj.text=@"111111111111";
TLog(@"obj", obj);
__weakMyObject *weakObj = obj;
TLog(@"weakObj-0", weakObj);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
__strongMyObject *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);
輸出
變量內(nèi)存地址:0x7fff572a9ab8, 變量值:0x7f9562f76210, 指向?qū)ο笾担?11111111111, -->obj
變量內(nèi)存地址:0x7fff572a9ab0, 變量值:0x7f9562f76210, 指向?qū)ο笾担?11111111111, -->weakObj-0
變量內(nèi)存地址:0x7fff572a9ab0, 變量值:0x7f9562f76210, 指向?qū)ο笾担?11111111111, -->weakObj-1
變量內(nèi)存地址:0x7f9562d143a0, 變量值:0x7f9562f76210, 指向?qū)ο笾担?11111111111, -->weakObj - block
變量內(nèi)存地址:0x116ccbe08, 變量值:0x7f9562f76210, 指向?qū)ο笾担?11111111111, -->strongObj - block
變量內(nèi)存地址:0x7fff572a9ab0, 變量值:0x0, 指向?qū)ο笾担?null), -->weakObj-2
如果你看過 AFNetworking 的源碼,會(huì)發(fā)現(xiàn) AFN 中作者會(huì)把變量在 block 外面先用__weak聲明,在 block 內(nèi)把前面 weak 聲明的變量賦值給__strong修飾的變量。這種寫法的好處就是可以讓變量在 block 內(nèi)部安全可用,即使外部釋放了,也會(huì)在 block 的生命周期內(nèi)保留該變量。這種寫法非常巧妙,既避免了循環(huán)引用的問題,又可以在 block 內(nèi)部持有該變量。
__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);
結(jié)果
變量內(nèi)存地址:0x7fff5021a9f0, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担?1111111111111, -->obj
變量內(nèi)存地址:0x7fff5021a9e8, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担?1111111111111, -->blockObj -1
<__NSMallocBlock__:0x7ff6b48d8c20>
變量內(nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担?1111111111111, -->blockObj -2
變量內(nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d8cd0, 指向?qū)ο笾担?1111111111111, -->blockObj - block
變量內(nèi)存地址:0x7fff5021a7f8, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担?22222222222, -->obj2
變量內(nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担?22222222222, -->blockObj - block
變量內(nèi)存地址:0x7ff6b48da518, 變量值:0x7ff6b48d9960, 指向?qū)ο笾担?22222222222, -->blockObj -3
我對(duì)__block的理解是其實(shí)際上是把變量的作用域給改變了,應(yīng)該是提升了變量的作用域,使得在 block 內(nèi)部和外部所訪問的是同一個(gè)變量。類似于聲明一個(gè) static 和 global 變量的意思。
之所以代碼中打印了一個(gè) testBlock 的類型,是因?yàn)榭梢钥吹皆?testBlock 聲明前,blockObj 的地址和聲明后的地址是有變化的,也就是說 blockObj 應(yīng)該是在 block 內(nèi)部提升的作用域。
再來看看__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);
輸出
變量內(nèi)存地址:0x7fff57eef9f0, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, -->obj
變量內(nèi)存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, -->blockObj - block
變量內(nèi)存地址:0x7ff86c918a88, 變量值:0x7ff86a55a160, 指向?qū)ο笾担?1111111111111, -->blockObj
當(dāng)外部 obj 指向 nil 的時(shí)候,obj 理應(yīng)被釋放,但實(shí)際上 blockObj 依然強(qiáng)引用著 obj,obj 其實(shí)并沒有被真正釋放。因此使用__block并不能避免循環(huán)引用的問題。
但是我們可以通過手動(dòng)釋放 blockObj 的方式來釋放 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)部能夠訪問到 obj,又可以避免循環(huán)引用的問題,但是這種方法也不是完美的,其存在下面幾個(gè)問題
必須記住在 block 底部釋放掉 block 變量,這其實(shí)跟 MRC 的形式有些類似了,不太適合 ARC
當(dāng)在 block 外部修改了 blockObj 時(shí),block 內(nèi)部的值也會(huì)改變,反之在 block 內(nèi)部修改 blockObj 在外部再使用時(shí)值也會(huì)改變。這就需要在寫代碼時(shí)注意這個(gè)特性可能會(huì)帶來的一些隱患
__block其實(shí)提升了變量的作用域,在 block 內(nèi)外訪問的都是同一個(gè) blockObj 可能會(huì)造成一些隱患
總結(jié)
__weak本身是可以避免循環(huán)引用的問題的,但是其會(huì)導(dǎo)致外部對(duì)象釋放了之后,block 內(nèi)部也訪問不到這個(gè)對(duì)象的問題,我們可以通過在 block 內(nèi)部聲明一個(gè)__strong的變量來指向 weakObj,使外部對(duì)象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題
__block本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動(dòng)把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點(diǎn)就是__block修飾的變量在 block 內(nèi)外都是唯一的,要注意這個(gè)特性可能帶來的隱患。
擴(kuò)展閱讀
block使用小結(jié)、在arc中使用block、如何防止循環(huán)引用
轉(zhuǎn)自:http://honglu.me/2015/01/06/weak%E4%B8%8Eblock%E5%8C%BA%E5%88%AB/