iOS __weak和__strong在Block中的使用

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即可

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末只冻,一起剝皮案震驚了整個(gè)濱河市蚁趁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖铜秆,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讶迁,死亡現(xiàn)場(chǎng)離奇詭異连茧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巍糯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門啸驯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祟峦,“玉大人罚斗,你說我怎么就攤上這事宅楞。” “怎么了厌衙?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我处铛,道長,這世上最難降的妖魔是什么撤蟆? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮龄砰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘换棚。我一直安慰自己反镇,他們只是感情好固蚤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布歹茶。 她就那樣靜靜地躺著你弦,像睡著了一般燎孟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揩页,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音碍沐,去河邊找鬼狸捅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛累提,可吹牛的內(nèi)容都是我干的尘喝。 我是一名探鬼主播斋陪,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缔赠!你這毒婦竟也來了友题?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤度宦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后离唬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡输莺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年裸诽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尸折。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡殷蛇,死狀恐怖橄浓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荸实,我是刑警寧澤缴淋,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站重抖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钟沛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一叁扫、第九天 我趴在偏房一處隱蔽的房頂上張望畜埋。 院中可真熱鬧莫绣,春花似錦悠鞍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硬鞍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間固该,已是汗流浹背糖儡。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留握联,地道東北人每瞒。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓纯露,卻偏偏與公主長得像,于是被迫代替她去往敵國和親埠褪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容