1. __weak使用
1.1 ARC以后引入了__weak
的概念來修飾Objective-C
對(duì)象,使用這個(gè)關(guān)鍵字修飾的對(duì)象,對(duì)象的引用計(jì)數(shù)不會(huì)+1,這個(gè)關(guān)鍵字和__unsafe_unretained
有些類似,只是在對(duì)象釋放的時(shí)候__weak
會(huì)將引用的對(duì)象置為nil
,而__unsafe_unretained
不會(huì),這將會(huì)導(dǎo)致野指針的產(chǎn)生,所以一般情況下,我們一般不屬于強(qiáng)引用某個(gè)對(duì)象的時(shí)候,可以使用__weak
進(jìn)行修飾,典型的例子就是代理.例如
class UICollectionView.h
@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
1.2 一個(gè)對(duì)象在聲明的時(shí)候,如果什么修飾符都不寫,默認(rèn)是strong,就會(huì)導(dǎo)致引用計(jì)數(shù)加+1,當(dāng)出了這個(gè)對(duì)象聲明的scope
,引用計(jì)數(shù)就會(huì)-1,如果引用計(jì)數(shù)為0,這個(gè)對(duì)象就會(huì)被釋放.
當(dāng)一個(gè)強(qiáng)引用的對(duì)象被Block捕獲的時(shí)候,引用計(jì)數(shù)就會(huì)增加,當(dāng)這個(gè)只有當(dāng)這個(gè)Block被釋放的時(shí)候這個(gè)強(qiáng)引用的對(duì)象的計(jì)數(shù)器才會(huì)回到原先的大小.
例如
void (^block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
TestObj *obj = [[TestObj alloc] init];
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對(duì)象地址:%@",obj);
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
}
block();
}
return 0;
}
打印的結(jié)果為:
DemoWeak[19005:6761339] before block retainCount:1
DemoWeak[19005:6761339] after block retainCount:3
DemoWeak[19005:6761339] TestObj對(duì)象地址:<TestObj: 0x602000006b30>
比較有意思的是引用計(jì)數(shù)并不是簡(jiǎn)單的+1,而是加2,這是由于block在創(chuàng)建的時(shí)候在棧上,而在賦值給全局變量的時(shí)候,被拷貝到了堆上,證明如下:
NSLog(@"堆%@",[block class]);
NSLog(@"棧%@",[^(){NSLog(@"TestObj對(duì)象地址:%@",obj);} class]);
DemoWeak[19026:6763489] 堆__NSMallocBlock__
DemoWeak[19026:6763489] 棧__NSStackBlock__
由于Block對(duì)對(duì)象的強(qiáng)引用,導(dǎo)致如果這個(gè)Block一直不釋放,那么所強(qiáng)引用的對(duì)象也就無法釋放,這樣就會(huì)導(dǎo)致對(duì)象的dealloc方法無法執(zhí)行,以前就遇到了這種對(duì)象不釋放,但仍然發(fā)送通知的情況,找了好久,解決這個(gè)問題也很簡(jiǎn)單,我們只需要將Block將要強(qiáng)引用的對(duì)象,弱引用就可以了,代碼如下
void (^block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對(duì)象地址:%@",weakObj);
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
}
block();
}
return 0;
}
打印結(jié)果如下
DemoWeak[19065:6774290] before block retainCount:1
DemoWeak[19065:6774290] after block retainCount:1
可以看到弱引用前后,對(duì)象的計(jì)數(shù)器并沒有什么改變.
剛開始學(xué)習(xí)Block的時(shí)候,總是覺得由于Block只是定義,而并沒有執(zhí)行,所以想當(dāng)然的以為Block并沒有對(duì)內(nèi)部的引用對(duì)象有任何影響,知道看到了Block編譯后的代碼,下面就是編譯后,截取的部分代碼
編譯后.png
從圖片上可以看出block在聲明的時(shí)候,就已經(jīng)調(diào)用了初始化函數(shù),保存了Block所捕獲到的引用對(duì)象(注意,由于使用__weak,無法編譯出.cpp源文件,所以在編譯時(shí)候,我使用了__unsafe_unretained,除了上面說到他和__weak之間的區(qū)別,實(shí)驗(yàn)的結(jié)果都是一樣的)
2. __strong使用
除了在Block外使用__weak
對(duì)對(duì)象進(jìn)行弱引用,我們偶爾還需要在Block內(nèi)部對(duì)弱引用對(duì)象進(jìn)行一次強(qiáng)引用,這是由于, 僅用__weak所修飾的對(duì)象,如果被釋放,那么這個(gè)對(duì)象在Block執(zhí)行的過程中就會(huì)變成nil,這就可能會(huì)帶來一些問題,比如,數(shù)組,字典的插入.
正確的做法是,在Block執(zhí)行的開始,檢驗(yàn)弱引用的對(duì)象是否還存在,如果還存在,使用__strong進(jìn)行強(qiáng)引用,這樣,在Block執(zhí)行的過程中,這個(gè)對(duì)象就不會(huì)被置為nil,而在Block執(zhí)行完畢后,對(duì)象的引用計(jì)數(shù)就會(huì)-1,這樣就不會(huì)導(dǎo)致對(duì)象無法釋放.
Block從外界所捕獲的對(duì)象和在Block內(nèi)部強(qiáng)使用__strong強(qiáng)引用的對(duì)象,差別就在于一個(gè)是在定義的時(shí)候就會(huì)影響對(duì)象的引用計(jì)數(shù)(理由就是上面編譯后的代碼),一個(gè)是在Block運(yùn)行的時(shí)候才強(qiáng)引用對(duì)象,執(zhí)行完畢還是會(huì)-1
一般情況下,只使用__weak就能滿足大部分的需求了,只有在多線程處理的時(shí)候,需要在Block使用下__strong修飾對(duì)象,因?yàn)?單個(gè)線程,要么執(zhí)行Block的時(shí)候?qū)ο筮€沒有被置為nil,那么直到Block被執(zhí)行完畢,這個(gè)對(duì)象都不會(huì)被釋放(釋放也是需要線程調(diào)用函數(shù)的不是?),但是在多線程的情況下,就可能造成,在執(zhí)行上半部分代碼的時(shí)候,對(duì)象還在,而在執(zhí)行下半部代碼的時(shí)候?qū)ο笠呀?jīng)被釋放,下面是一個(gè)例子:
TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對(duì)象地址:%@",weakObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模擬一個(gè)耗時(shí)的任務(wù)
}
NSLog(@"耗時(shí)的任務(wù) 結(jié)束 TestObj對(duì)象地址:%@",weakObj);
});
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
block();
打印結(jié)果:
DemoWeek[19247:6816518] before block retainCount:1
DemoWeek[19247:6816518] after block retainCount:1
DemoWeek[19247:6816518] TestObj對(duì)象地址:<TestObj: 0x602000006af0>
DemoWeek[19247:6816518] TestObj 對(duì)象已釋放
DemoWeek[19247:6816544] 耗時(shí)的任務(wù) 結(jié)束 TestObj對(duì)象地址:(null)
可以看到在耗時(shí)任務(wù)執(zhí)行前對(duì)象還是存在的,只是在執(zhí)行完畢了后,對(duì)象被釋放了,如果我們使用__strong修飾就可以避免這種情況
block = ^(){
__strong TestObj *strongObj = weakObj;
if(! strongObj) return;
NSLog(@"TestObj對(duì)象地址:%@",strongObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模擬一個(gè)耗時(shí)的任務(wù)
}
NSLog(@"耗時(shí)的任務(wù) 結(jié)束 TestObj對(duì)象地址:%@",strongObj);
});
打印結(jié)果:
DemoWeek[19280:6819437] before block retainCount:1
DemoWeek[19280:6819437] after block retainCount:1
DemoWeek[19280:6819437] TestObj對(duì)象地址:<TestObj: 0x602000006b30>
DemoWeek[19280:6819464] 耗時(shí)的任務(wù) 結(jié)束 TestObj對(duì)象地址:<TestObj: 0x602000006b30>
DemoWeek[19280:6819464] TestObj 對(duì)象已釋放
總結(jié):
__weak修飾的對(duì)象被Block引用,不會(huì)影響對(duì)象的釋放,而__strong在Block內(nèi)部修飾的對(duì)象,會(huì)保證,在使用這個(gè)對(duì)象在scope內(nèi),這個(gè)對(duì)象都不會(huì)被釋放,出了scope,引用計(jì)數(shù)就會(huì)-1,且__strong主要是用在多線程運(yùn)用中,若果只使用單線程,只需要使用__weak即可