iOS循環(huán)引用

循環(huán)引用,故名思義始锚,即強(qiáng)引用的引用鏈上出現(xiàn)了環(huán)喳逛。A、B兩個(gè)對(duì)象姐呐,A強(qiáng)引用了B典蝌,B又強(qiáng)引用了A,導(dǎo)致在任何時(shí)候A麦轰、B的引用計(jì)數(shù)都不為0,始終不會(huì)被釋放款侵。解決循環(huán)引用的一般方法通過將Strong指針改為weak指針從而打破環(huán)新锈。

  • delegate循環(huán)引用
    例如有A眶熬、B、C三個(gè)控制器拳缠,其中B有個(gè)Strong屬性C贸弥,C有個(gè)Strong屬性delegate,該代理指向B哲鸳】控制器進(jìn)行如下跳轉(zhuǎn),
    A--push-->B--push-->C郁岩,之后再執(zhí)行pop
    當(dāng)從C--pop-->B時(shí)萍摊,
    C不會(huì)執(zhí)行dealloc函數(shù)蝴乔,原因是由于B還持有C的強(qiáng)引用,并且B沒有被釋放片酝,
    此時(shí)挖腰,當(dāng)從B--pop-->A時(shí),
    B不會(huì)執(zhí)行dealloc函數(shù)审轮,原因是由于C的代理是強(qiáng)引用,而這個(gè)代理對(duì)象正是B篡诽,因此出現(xiàn)了強(qiáng)引用環(huán)榴捡,
    B<--強(qiáng)引用-->C
    打破環(huán)的方法為达椰,可以將C的代理使用weak修飾詞修飾项乒,
    這樣當(dāng)C--pop-->B時(shí),
    C依然不會(huì)執(zhí)行dealloc函數(shù)蝇裤,原因仍是由于B還活著并且持有C的強(qiáng)引用埃碱,
    而當(dāng)從B--pop-->A時(shí),
    由于沒有強(qiáng)引用指針指向B了啃憎,所以B先釋放了似炎,之后由于B的釋放,也就沒有了強(qiáng)引用贩毕,導(dǎo)致C也釋放了仆嗦。

補(bǔ)充:細(xì)心的你也許會(huì)提出這樣的疑問,為什么沒有強(qiáng)引用指針指向B了?這里的原因是從A--push-->B時(shí)谆甜,navigationController有個(gè)viewControllers的屬性集绰,這個(gè)屬性是一個(gè)數(shù)組,當(dāng)執(zhí)行A--push-->B操作時(shí)栽燕,會(huì)向這個(gè)數(shù)組中添加一個(gè)元素,這個(gè)元素也就是你push的這個(gè)控制器B浴讯,我們都知道數(shù)組的addObject會(huì)使引用計(jì)數(shù)+1,因此在A--push-->B操作后侍郭,就持有了B的強(qiáng)引用掠河,當(dāng)調(diào)用B--pop-->A操作后執(zhí)行到viewWillDisappear函數(shù)時(shí)唠摹,已經(jīng)將BviewControllers數(shù)組中移除奉瘤,此時(shí)B已經(jīng)沒有被任何強(qiáng)引用指針?biāo)钟校?code>B執(zhí)行dealloc函數(shù)。

除此之外看到這里你也許還會(huì)問盗温,為什么筆者要使用3個(gè)控制器來描述卖局,而非2個(gè)控制器呢?那么接下來請你思考如下的情景是否存在引用循環(huán):
兩個(gè)控制器A砚偶、B染坯,其中B有個(gè)Strong屬性delegate,該代理指向A单鹿≈俪控制器進(jìn)行如下跳轉(zhuǎn),
A--push-->B昼窗,之后再執(zhí)行pop澄惊。
當(dāng)B--pop-->A時(shí)富雅,
由于沒有任何強(qiáng)引用持有B肛搬,雖然B持有A的強(qiáng)引用,但是這并不影響B的釋放蛤奢,因此B仍然會(huì)執(zhí)行dealloc函數(shù)陶贼。

  • block循環(huán)引用
    說起block的循環(huán)引用,就需要談一下block的實(shí)現(xiàn)原理痹屹。
    這里推薦一篇介紹block原理非常棒的文章枉氮,iOS底層原理總結(jié) - 探尋block的本質(zhì)(一),剛開始看文章會(huì)覺得有些晦澀難懂楼肪,但是反復(fù)看兩遍就可以理解了惹悄,真的寫的非常好。
    有一個(gè)結(jié)論就是block最終都是繼承自NSBlock類型象缀,而NSBlock繼承于NSObjcet爷速,因此block本質(zhì)是一個(gè)OC對(duì)象。

補(bǔ)充:查看源碼方式
首先在main.m文件中寫一個(gè)block

int main(int argc, const char * argv[]) {
@autoreleasepool {
        int age = 10;
        void(^block)(int ,int) = ^(int a, int b){
            NSLog(@"this is block,a = %d,b = %d",a,b);
            NSLog(@"this is block,age = %d",age);
        };
        block(3,5);
    }
    return 0;
}

然后我們在終端執(zhí)行如下命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m  

然后就可以看到c++中block的聲明和定義分別與oc代碼中相對(duì)應(yīng)顯示了莉给。


1434508-8c9c32b581bccf34.png

通過源碼發(fā)現(xiàn)颓遏,在block定義時(shí)調(diào)用了一個(gè)__main_block_impl_0函數(shù)滞时,并且將該函數(shù)的地址賦值給了block,那么這個(gè)函數(shù)又是什么呢曼玩?

1434508-beb290d0acd377b1.png

__main_block_impl_0是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)包括一個(gè)同名構(gòu)造函數(shù)豫尽,還有另外兩個(gè)結(jié)構(gòu)體以及一個(gè)成員變量age顷帖。這個(gè)同名的構(gòu)造函數(shù)接收了幾個(gè)參數(shù)。
我們看一下*fp這個(gè)參數(shù)榴嗅,這是一個(gè)函數(shù)指針陶舞,block將我們在block塊中寫的代碼封裝成了一個(gè)函數(shù),然后將這個(gè)函數(shù)的地址傳入了__main_block_impl_0的構(gòu)造函數(shù)中保存在結(jié)構(gòu)體內(nèi)。
另外age是我們定義的局部變量颁井,在block訪問該變量時(shí)蠢护,block會(huì)對(duì)其進(jìn)行捕獲,那么什么是捕獲呢葵硕?我個(gè)人的理解是對(duì)其進(jìn)行拷貝操作懈凹,這里的拷貝分為深拷貝和淺拷貝兩種,對(duì)于局部變量介评,由于怕使用指針訪問時(shí)該變量被釋放们陆,因此會(huì)在第一次拿到該變量時(shí)進(jìn)行值拷貝;而對(duì)于靜態(tài)變量坪仇,由于該變量不會(huì)自動(dòng)被釋放椅文,因此可以直接通過指針方式進(jìn)行拷貝惜颇;而對(duì)于全局變量恤筛,block在訪問時(shí)并沒有對(duì)其進(jìn)行拷貝操作,而是直接訪問望伦。通過如下示例可以說明:

int a = 10;
static int b = 11;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int c = 12;
        void(^block)(void) = ^{
            NSLog(@"hello, a = %d, b = %d, c = %d", a, b, c);
        };
        a = 1;
        b = 2;
        c = 3;
        block();
    }
    return 0;
}
// 控制臺(tái)輸出 a = 1, b = 2, c = 12

總結(jié)如下:


1434508-fc81811bcf0e5398.png

疑問:以下代碼中block是否會(huì)捕獲變量呢屯伞?

#import "Person.h"
@implementation Person
- (void)test {
    void(^block)(void) = ^{
        NSLog(@"%@", self.name);
        NSLog(@"%@", _name);
    };
    block();
}

不論對(duì)象方法還是類方法都會(huì)默認(rèn)將self作為參數(shù)傳遞給方法內(nèi)部豪直,既然是作為參數(shù)傳入,那么self肯定是局部變量末融。上面講到局部變量肯定會(huì)被block捕獲暇韧。
對(duì)于block中使用的是實(shí)例對(duì)象的屬性時(shí),block捕獲的是實(shí)例對(duì)象巧婶,并通過實(shí)例對(duì)象的方法選擇器去獲取使用到的屬性涂乌;而對(duì)于block中使用的是實(shí)例對(duì)象的成員變量來說,block捕獲的仍然是實(shí)例對(duì)象湿右,然后通過成員變量的地址訪問罚勾。
因此,導(dǎo)致block會(huì)發(fā)生循環(huán)引用的問題來了堰塌,
也就是說分衫,如果某個(gè)類強(qiáng)引用了某個(gè)block,又在這個(gè)block中訪問了這個(gè)類牵现,就會(huì)造成引用循環(huán),因?yàn)閎lock會(huì)對(duì)內(nèi)部的實(shí)例對(duì)象進(jìn)行捕獲科乎,這種捕獲是一個(gè)強(qiáng)引用贼急。
而解決這個(gè)引用循環(huán)的方式也很簡單,就是使用一個(gè)weak指針修飾一下將要在block中使用的對(duì)象就可以了空闲。

  • Timer循環(huán)引用
    NSTimer 的 target 對(duì)傳入的參數(shù)都是強(qiáng)引用(即使是 weak 對(duì)象)


    6618656-d08f3092a97ab9e3.png

    解決方法:在不需要timer的時(shí)候調(diào)用一下invalidate方法即可碴倾。

4.1 對(duì)于block掉丽,是否都需要使用weakSelf來解決循環(huán)引用問題?

當(dāng)block本身不被self持有,而被別的對(duì)象持有捶障,同時(shí)不產(chǎn)生循環(huán)引用的時(shí)候残邀,就不需要使用weakSelf了柑蛇。
例如UIView的某個(gè)負(fù)責(zé)動(dòng)畫的對(duì)象持有了 block
block 持有了 self
因?yàn)閟elf并不持有block,所以就沒有循環(huán)引用產(chǎn)生空免,就不需要使用weakSelf了盆耽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摄杂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子析恢,更是在濱河造成了極大的恐慌映挂,老刑警劉巖盗尸,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帽撑,死亡現(xiàn)場離奇詭異亏拉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)专筷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門磷蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庇勃,你說我怎么就攤上這事槽驶。” “怎么了罕拂?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵全陨,是天一觀的道長辱姨。 經(jīng)常有香客問我,道長雨涛,這世上最難降的妖魔是什么替久? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旧困,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘僚纷。我一直安慰自己拗盒,他們只是感情好占键,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布辛辨。 她就那樣靜靜地躺著拷邢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸦致。 梳的紋絲不亂的頭發(fā)上涣楷,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天狮斗,我揣著相機(jī)與錄音,去河邊找鬼碳褒。 笑死沙峻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的专酗。 我是一名探鬼主播祷肯,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼疗隶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蒋纬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤关摇,失蹤者是張志新(化名)和其女友劉穎碾阁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宪睹,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亭病,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嘶居,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食听。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖樱报,靈堂內(nèi)的尸體忽然破棺而出葬项,到底是詐尸還是另有隱情,我是刑警寧澤迹蛤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布民珍,位于F島的核電站,受9級(jí)特大地震影響盗飒,放射性物質(zhì)發(fā)生泄漏嚷量。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一逆趣、第九天 我趴在偏房一處隱蔽的房頂上張望蝶溶。 院中可真熱鬧,春花似錦宣渗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鞍恢,卻和暖如春傻粘,著一層夾襖步出監(jiān)牢的瞬間每窖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工弦悉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窒典,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓警绩,卻偏偏與公主長得像崇败,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肩祥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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