一文弄懂iOS中的循環(huán)引用

三種類型循環(huán)引用

1.自循環(huán)引用
2.相互循環(huán)引用
3.多循環(huán)引用

自循環(huán)引用

假如有一個(gè)對(duì)象,內(nèi)部強(qiáng)持有它的成員變量obj,
若此時(shí)我們給obj賦值為原對(duì)象時(shí),就是自循環(huán)引用埠对。

相互循環(huán)引用

對(duì)象A內(nèi)部強(qiáng)持有obj,對(duì)象B內(nèi)部強(qiáng)持有obj,
若此時(shí)對(duì)象A的obj指向?qū)ο驜,同時(shí)對(duì)象B中的obj指向?qū)ο驛,就是相互引用。

多循環(huán)引用

假如類中有對(duì)象1...對(duì)象N,每個(gè)對(duì)象中都強(qiáng)持有一個(gè)obj,
若每個(gè)對(duì)象的obj都指向下個(gè)對(duì)象,就產(chǎn)生了多循環(huán)引用劣像。

常見(jiàn)循環(huán)引用

1.代理
2.Block
3.NSTimer
4.大環(huán)引用

如何破除循環(huán)引用

1.避免產(chǎn)生循環(huán)引用。
在使用代理時(shí),兩個(gè)對(duì)象,一個(gè)強(qiáng)引用,一個(gè)弱引用,避免產(chǎn)生相互循環(huán)引用停忿。
2.在合適的時(shí)機(jī)手動(dòng)斷環(huán)。

具體方案

1.__weak
2.__block
3.__unsafe_unretained 用這個(gè)的關(guān)鍵字修飾的對(duì)象也沒(méi)有增加引用計(jì)數(shù),和__weak在效果上是等效的蚊伞。

__weak破解

對(duì)象B會(huì)強(qiáng)持有A,對(duì)象A弱引用B

__block破解

__block在ARC和MRC中是不同的:

  • MRC下,__block修飾對(duì)象不會(huì)增加其引用計(jì)數(shù),避免了循環(huán)引用席赂。
  • ARC下,__block修飾對(duì)象會(huì)被強(qiáng)引用,無(wú)法避免循環(huán)引用,需手動(dòng)解環(huán)。

__unsafe_unretained破解

1.修飾對(duì)象不會(huì)增加其引用計(jì)數(shù),避免了循環(huán)引用时迫。
2.如果被修飾的對(duì)象在某一時(shí)機(jī)被釋放,會(huì)產(chǎn)生懸垂指針,再通過(guò)這個(gè)指針去訪問(wèn)原對(duì)象的話,會(huì)導(dǎo)致內(nèi)存泄露,所以一般不建議用颅停,__unsafe_unretained去解除循環(huán)引用。

循環(huán)引用示例

NSTimer

假如VC中有個(gè)廣告欄,需要1S中滾動(dòng)一次播放下一個(gè)廣告,我們會(huì)把這個(gè)廣告欄的UI對(duì)象作為VC的成員變量,由VC進(jìn)行強(qiáng)持有掠拳。
因?yàn)閺V告欄每隔1S需要滾動(dòng)播放,則廣告欄中會(huì)添加成員變量NSTimer并強(qiáng)引用,當(dāng)分配定時(shí)回調(diào)事件之后,NSTimer會(huì)對(duì)廣告欄的Target進(jìn)行強(qiáng)引用,就產(chǎn)生了相互循環(huán)引用癞揉。

如果把對(duì)象對(duì)NSTimer的強(qiáng)引用改為弱引用,是無(wú)法解決問(wèn)題的,原因如下圖:

因?yàn)楫?dāng)NSTimer被分配之后,會(huì)被當(dāng)前線程的Runloop進(jìn)行強(qiáng)引用,
如果對(duì)象以及NSTimer是在主線程創(chuàng)建的,就會(huì)被主線程的Runloop持有這個(gè)NSTimer,所以即使我們?cè)趶V告欄中通過(guò)弱引用來(lái)指向NSTimer,但是由于主線程中Runloop常駐內(nèi)存通過(guò)對(duì)NSTimer的強(qiáng)引用,再通過(guò)NSTimer對(duì)對(duì)象的強(qiáng)引用,仍然對(duì)這個(gè)對(duì)象產(chǎn)生了強(qiáng)引用,此時(shí)即使VC頁(yè)面退出,去掉VC對(duì)對(duì)象的引用,當(dāng)前廣告欄仍然有被Runloop的間接強(qiáng)引用持有,這個(gè)對(duì)象也不會(huì)被釋放,此時(shí)就產(chǎn)生了內(nèi)存泄露。

解決方法:NSTimer分重復(fù)定時(shí)器和非重復(fù)定時(shí)器

非重復(fù)定時(shí)器:

在定時(shí)器的回調(diào)方法中去調(diào)用[NSTimer invalid]同時(shí)將NSTimer置為nil,可以將Runloop對(duì)NSTimer的強(qiáng)引用解除掉,同時(shí)NSTimer也解除了對(duì)對(duì)象的強(qiáng)引用溺欧。

重復(fù)定時(shí)器:

不能在定時(shí)器的回調(diào)方法中去調(diào)用[NSTimer invalid]以及將NSTimer置為nil操作,此時(shí)的解決方案是:
左側(cè)是Runloop對(duì)NSTimer的強(qiáng)引用,右側(cè)是VC對(duì)對(duì)象的強(qiáng)引用,
可以在NSTimer和對(duì)象中間添加一個(gè)中間對(duì)象,然后由NSTimer對(duì)中間對(duì)象進(jìn)行強(qiáng)引用,
同時(shí)中間對(duì)象分別對(duì)NSTimer和廣告欄對(duì)象進(jìn)行弱引用,那么對(duì)于重復(fù)對(duì)象而已,
當(dāng)當(dāng)前VC退出之后,VC就釋放了對(duì)廣告欄對(duì)象的強(qiáng)引用,
當(dāng)下次定時(shí)器的回調(diào)事件回來(lái)的時(shí)候,可以在中間對(duì)象當(dāng)中,判斷當(dāng)前中間對(duì)象所持有的弱引用廣告欄對(duì)象是否被釋放了,
實(shí)際上就是判斷中間對(duì)象當(dāng)中所持有的weak變量是否為nil,
如果是nil,然后調(diào)用[NSTimer invalid]以及將NSTimer置為nil,
就打破了Runloop對(duì)NSTimer的強(qiáng)引用以及NSTimer對(duì)中間對(duì)象的強(qiáng)引用
這個(gè)解決方案是利用了:當(dāng)一個(gè)對(duì)象被釋放后,它的weak指針會(huì)自動(dòng)置為nil喊熟。

中間對(duì)象TimerWeakObject的實(shí)現(xiàn)

//NSTimer的NSTimer類別
#import <Foundation/Foundation.h>
@interface NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats;
 
@end
 
 
 
#import "NSTimer+WeakTimer.h"
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;//中間對(duì)象的弱引用指針
@property (nonatomic, assign) SEL selector;//定時(shí)器到時(shí)之后的一個(gè)回調(diào)方法
@property (nonatomic, weak) NSTimer *timer;//中間對(duì)象的弱引用指針
 
- (void)fire:(NSTimer *)timer;
@end
 
@implementation TimerWeakObject
/*對(duì)它所持有的target進(jìn)行判斷,若target存在,判斷它是否響應(yīng)選擇器,
  如果響應(yīng)則執(zhí)行對(duì)應(yīng)的回調(diào)方法
  否則就把timer置為無(wú)效,就可以達(dá)到Runloop對(duì)Timer強(qiáng)引用的釋放,同時(shí)Timer也會(huì)對(duì)弱引用對(duì)象進(jìn)行釋放
*/
- (void)fire:(NSTimer *)timer
{
    if (self.target) {
        if ([self.target respondsToSelector:self.selector]) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        }
    }
    else{
        [self.timer invalidate];
    }
}
 
@end
 
//分類中的具體實(shí)現(xiàn)
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats
{
/*
   創(chuàng)建中間對(duì)象,把我們傳進(jìn)分類中的aTarget和aSelector指派給中間對(duì)象,
   然后調(diào)用系統(tǒng)的NSTimer方法去創(chuàng)建NSTimer,
   同時(shí)指定Timer的回調(diào)事件是中間對(duì)象的fire方法,
  然后再fire方法中再對(duì)實(shí)際對(duì)象回調(diào)方法進(jìn)行調(diào)用
*/
    TimerWeakObject *object = [[TimerWeakObject alloc] init];
    object.target = aTarget;
    object.selector = aSelector;
    object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    
    return object.timer;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市姐刁,隨后出現(xiàn)的幾起案子芥牌,更是在濱河造成了極大的恐慌,老刑警劉巖聂使,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁拉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡柏靶,警方通過(guò)查閱死者的電腦和手機(jī)弃理,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)屎蜓,“玉大人痘昌,你說(shuō)我怎么就攤上這事【孀” “怎么了控汉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)返吻。 經(jīng)常有香客問(wèn)我姑子,道長(zhǎng),這世上最難降的妖魔是什么测僵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任街佑,我火速辦了婚禮谢翎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沐旨。我一直安慰自己森逮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布磁携。 她就那樣靜靜地躺著褒侧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谊迄。 梳的紋絲不亂的頭發(fā)上闷供,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音统诺,去河邊找鬼歪脏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛粮呢,可吹牛的內(nèi)容都是我干的婿失。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼啄寡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼豪硅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挺物,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舟误,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后姻乓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體嵌溢,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蹋岩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赖草。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剪个,死狀恐怖秧骑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扣囊,我是刑警寧澤乎折,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站侵歇,受9級(jí)特大地震影響骂澄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惕虑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一坟冲、第九天 我趴在偏房一處隱蔽的房頂上張望磨镶。 院中可真熱鬧,春花似錦健提、人聲如沸琳猫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脐嫂。三九已至,卻和暖如春紊遵,著一層夾襖步出監(jiān)牢的瞬間账千,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工癞蚕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕊爵,地道東北人辉哥。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓桦山,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親醋旦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恒水,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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