singleton是實例對象, self 持有 singleton , singleton持有Block, Block持有 self ,也可直接理解為 self 持有 singleton , singleton 持有 self,當(dāng)self需要釋放的時候,singleton是不需要釋放的,但是 singleton 持有self 導(dǎo)致 self 不能被釋放,因此,self 無法被釋放,導(dǎo)致內(nèi)存泄漏。
成都創(chuàng)新互聯(lián)公司為企業(yè)級客戶提高一站式互聯(lián)網(wǎng)+設(shè)計服務(wù),主要包括成都網(wǎng)站建設(shè)、成都做網(wǎng)站、成都App定制開發(fā)、重慶小程序開發(fā)公司、宣傳片制作、LOGO設(shè)計等,幫助客戶快速提升營銷能力和企業(yè)形象,創(chuàng)新互聯(lián)各部門都有經(jīng)驗豐富的經(jīng)驗,可以確保每一個作品的質(zhì)量和創(chuàng)作周期,同時每年都有很多新員工加入,為我們帶來大量新的創(chuàng)意。
一、Block 回調(diào)造成的循環(huán)引用
二、NSTimer 強持有self
解決辦法,使用 __weak typeof(self)weakSelf = self; 弱化self,打破循環(huán)引用(必要的時候我們還需要在block內(nèi)部聲明 strongSelf ,為防止weakSelf因為某種原因在block里提前釋放使得weakSelf=nil,變成野指針,導(dǎo)致后邊再調(diào)用weakSelf 造成崩潰)。示例如下
Timer 銷毀請看此文: iOS Timer 詳解
循環(huán)引用的實質(zhì):多個對象相互之間有強引用,不能釋放讓系統(tǒng)回收。
如何解決循環(huán)引用?
1、避免產(chǎn)生循環(huán)引用,通常是將 strong 引用改為 weak 引用。
比如在修飾屬性時用weak
在block內(nèi)調(diào)用對象方法時,使用其弱引用,這里可以使用兩個宏
#defineWS(weakSelf)? ? ? ? ? ? __weak __typeof(*self)weakSelf = self;// 弱引用#defineST(strongSelf)? ? ? ? ? __strong __typeof(*self)strongSelf = weakSelf;//使用這個要先聲明weakSelf
還可以使用__block來修飾變量
在MRC下,__block不會增加其引用計數(shù),避免了循環(huán)引用
在ARC下,__block修飾對象會被強引用,無法避免循環(huán)引用,需要手動解除。
2、在合適時機去手動斷開循環(huán)引用。
通常我們使用第一種。
循環(huán)引用場景:
1. 自循環(huán)引用
對象強持有的屬性同時持有該對象
2. 相互循環(huán)引用
3. 多循環(huán)引用
1、代理(delegate)循環(huán)引用屬于相互循環(huán)引用
delegate 是iOS中開發(fā)中比較常遇到的循環(huán)引用,一般在聲明delegate的時候都要使用弱引用 weak,或者assign,當(dāng)然怎么選擇使用assign還是weak,MRC的話只能用assign,在ARC的情況下最好使用weak,因為weak修飾的變量在釋放后自動指向nil,防止野指針存在
2、NSTimer循環(huán)引用屬于相互循環(huán)使用
在控制器內(nèi),創(chuàng)建NSTimer作為其屬性,由于定時器創(chuàng)建后也會強引用該控制器對象,那么該對象和定時器就相互循環(huán)引用了。
如何解決呢?
這里我們可以使用手動斷開循環(huán)引用:
如果是不重復(fù)定時器,在回調(diào)方法里將定時器invalidate并置為nil即可。
如果是重復(fù)定時器,在合適的位置將其invalidate并置為nil即可
3、block循環(huán)引用
一個簡單的例子:
由于block會對block中的對象進行持有操作,就相當(dāng)于持有了其中的對象,而如果此時block中的對象又持有了該block,則會造成循環(huán)引用。
解決方案就是使用__weak修飾self即可
并不是所有block都會造成循環(huán)引用。
只有被強引用了的block才會產(chǎn)生循環(huán)引用
而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]這些系統(tǒng)方法等
或者block并不是其屬性而是臨時變量,即棧block
還有一種場景,在block執(zhí)行開始時self對象還未被釋放,而執(zhí)行過程中,self被釋放了,由于是用weak修飾的,那么weakSelf也被釋放了,此時在block里訪問weakSelf時,就可能會發(fā)生錯誤(向nil對象發(fā)消息并不會崩潰,但也沒任何效果)。
對于這種場景,應(yīng)該在block中對 對象使用__strong修飾,使得在block期間對 對象持有,block執(zhí)行結(jié)束后,解除其持有。
1.自循環(huán)引用
2.相互循環(huán)引用
3.多循環(huán)引用
假如有一個對象,內(nèi)部強持有它的成員變量obj,
若此時我們給obj賦值為原對象時,就是自循環(huán)引用。
對象A內(nèi)部強持有obj,對象B內(nèi)部強持有obj,
若此時對象A的obj指向?qū)ο驜,同時對象B中的obj指向?qū)ο驛,就是相互引用。
假如類中有對象1...對象N,每個對象中都強持有一個obj,
若每個對象的obj都指向下個對象,就產(chǎn)生了多循環(huán)引用。
1.代理
2.Block
3.NSTimer
4.大環(huán)引用
1.避免產(chǎn)生循環(huán)引用。
在使用代理時,兩個對象,一個強引用,一個弱引用,避免產(chǎn)生相互循環(huán)引用。
2.在合適的時機手動斷環(huán)。
1.__weak
2.__block
3.__unsafe_unretained 用這個的關(guān)鍵字修飾的對象也沒有增加引用計數(shù),和__weak在效果上是等效的。
對象B會強持有A,對象A弱引用B
__block在ARC和MRC中是不同的:
1.修飾對象不會增加其引用計數(shù),避免了循環(huán)引用。
2.如果被修飾的對象在某一時機被釋放,會產(chǎn)生 懸垂指針 ,再通過這個指針去訪問原對象的話,會導(dǎo)致內(nèi)存泄露,所以一般不建議用,__unsafe_unretained去解除循環(huán)引用。
NSTimer
假如VC中有個廣告欄,需要1S中滾動一次播放下一個廣告,我們會把這個廣告欄的UI對象作為VC的成員變量,由VC進行強持有。
因為廣告欄每隔1S需要滾動播放,則廣告欄中會添加成員變量NSTimer并強引用,當(dāng)分配定時回調(diào)事件之后,NSTimer會對廣告欄的Target進行強引用,就產(chǎn)生了相互循環(huán)引用。
如果把對象對NSTimer的強引用改為弱引用,是無法解決問題的, 原因如下圖:
因為當(dāng)NSTimer被分配之后,會 被當(dāng)前線程的Runloop進行強引用 ,
如果對象以及NSTimer是在主線程創(chuàng)建的,就會被主線程的Runloop持有這個NSTimer,所以即使我們在廣告欄中通過弱引用來指向NSTimer,但是由于主線程中Runloop常駐內(nèi)存通過對NSTimer的強引用,再通過NSTimer對對象的強引用,仍然對這個對象產(chǎn)生了強引用,此時即使VC頁面退出,去掉VC對對象的引用,當(dāng)前廣告欄仍然有被Runloop的間接強引用持有,這個對象也不會被釋放,此時就產(chǎn)生了內(nèi)存泄露。
解決方法:NSTimer分重復(fù)定時器和非重復(fù)定時器
在定時器的回調(diào)方法中去調(diào)用 [NSTimer invalid] 同時將 NSTimer置為nil ,可以將Runloop對NSTimer的強引用解除掉,同時NSTimer也解除了對對象的強引用。
不能在定時器的回調(diào)方法中去調(diào)用[NSTimer invalid]以及將NSTimer置為nil操作,此時的解決方案是:
左側(cè)是Runloop對NSTimer的強引用,右側(cè)是VC對對象的強引用,
可以在NSTimer和對象中間 添加一個中間對象 ,然后由NSTimer對中間對象進行強引用,
同時中間對象分別對NSTimer和廣告欄對象進行弱引用,那么對于重復(fù)對象而已,
當(dāng)當(dāng)前VC退出之后,VC就釋放了對廣告欄對象的強引用,
當(dāng)下次定時器的回調(diào)事件回來的時候,可以在中間對象當(dāng)中,判斷當(dāng)前中間對象所持有的弱引用廣告欄對象是否被釋放了,
實際上就是判斷中間對象當(dāng)中所持有的weak變量是否為nil,
如果是nil,然后調(diào)用[NSTimer invalid]以及將NSTimer置為nil,
就打破了Runloop對NSTimer的強引用以及NSTimer對中間對象的強引用
這個解決方案是利用了:當(dāng)一個對象被釋放后,它的weak指針會自動置為nil。
中間對象TimerWeakObject的實現(xiàn)
-- :表示弱引用。
- :表示強引用。
循環(huán)引用可以簡單理解為對象A引用了對象B,而對象B又引用了對象A:A - B - A,此時雙方都同時保持對方的一個引用,導(dǎo)致任何時候雙方的引用計數(shù)都不為0,雙方始終無法釋放就造成內(nèi)存泄漏。
當(dāng)然不只是兩個對象之間相互引用會形成循環(huán)引用,多個對象之間相互引用最終形成環(huán)同樣會形成循環(huán)引用。
例如:A-B-C-....-X-B。
循環(huán)引用對 app 有潛在的危害,會使內(nèi)存消耗過高,導(dǎo)致內(nèi)存泄漏,性能變差和 app 閃退等。
block 、 delegate 、NSTimer
self.tableView.delegate = self;
如果 delegate使用strong修飾就會構(gòu)成循環(huán)引用:self - tableView - delegate - self。
所以在定義delegate屬性時使用weak便能解決這一問題:self - tableView -- delegate - self。tableView和delegate之間不是強引用,所以構(gòu)不成循環(huán)。
規(guī)避delegate循環(huán)引用的殺手锏也是簡單到哭:定義delegate屬性時請用assign(MRC)或者weak(ARC),千萬別手賤玩一下retain或者strong。
(1)并不是所有block都會產(chǎn)生循環(huán)引用,block是否產(chǎn)生循環(huán)引用是需要我們?nèi)ヅ袛嗟模?/p>
(2)self - reachabilityManager - block - self,才會產(chǎn)生循環(huán)引用,并且XCode給出了循環(huán)引用warning,例如
(3)解決block循環(huán)引用的方法是使用__weak修飾self,然后在block里使用被修飾后的weakSelf來代替self:
1、合適的時機啟動和銷毀 NSTimer
解決 NSTimer 的循環(huán)引用,我們首先會想到的方法應(yīng)該是在 OneViewController dealloc 之前就銷毀 NSTimer,這樣循環(huán)就被打破了。
最簡單的方法就是在 viewWillAppear 中啟動 NSTimer,然后在 viewWillDisappear 中銷毀 NSTimer,成對出現(xiàn),絕對沒有問題。
2、Effective Objective-C ”中的52條方法
計時器保留其目標(biāo)對象,反復(fù)執(zhí)行任務(wù)導(dǎo)致的循環(huán),確實要注意,另外在dealloc的時候,不要忘了調(diào)用計時器中的 invalidate方法。
循環(huán)引用,指的是多個對象相互引用時,使得引用形成一個環(huán)形,導(dǎo)致外部無法真正是否掉這塊環(huán)形內(nèi)存。其實有點類似死鎖。
舉個例子:A-B-C-....-X-B -表示強引用,這樣的B的引用計數(shù)就是2,假如A被系統(tǒng)釋放了,理論上A會自動減小A所引用的資源,就是B,那么這時候B的引用計數(shù)就變成了1,所有B無法被釋放,然而A已經(jīng)被釋放了,所有B的內(nèi)存部分就肯定無法再釋放再重新利用這部分內(nèi)存空間了,導(dǎo)致內(nèi)存泄漏。
情況一:delegate
Delegate是ios中開發(fā)中最常遇到的循環(huán)引用,一般在聲明delegate的時候都要使用弱引用weak或者assign
@property (nonatomic, weak, nullable) id UITableViewDelegate delegate;
當(dāng)然怎么選擇使用assign還是weak,MRC的話只能用assign,在ARC的情況下最好使用weak,因為weak修飾的變量在是否后自動為指向nil,防止不安全的野指針存在
情況二:Block
Block也是比較常見的循環(huán)引用問題,在Block中使用了self容易出現(xiàn)循環(huán)引用,因此很多人在使用block的時候,加入里面有用到self的操作都會聲明一個__weak來修飾self。其實便不是這樣的,不是所有使用了Block都會出現(xiàn)Self循環(huán)引用問題,只有self擁有Block的強引用才會出現(xiàn)這種情況。
所以一般在函數(shù)中臨時使用Block是不會出現(xiàn)循環(huán)應(yīng)用的,因為這時候Block引用是屬于棧的。當(dāng)棧上的block釋放后,block中對self的引用計數(shù)也會減掉
當(dāng)然不一定要Self對Block有直接的引用才會出現(xiàn),假如self的變量B,B中有個Block變量,就容易出現(xiàn)這種情況,好的是在block出現(xiàn)循環(huán)引用的,xcode7會出現(xiàn)警告提示(之前版本不確定)。
情況三:NSTimer
這是一個神奇的NSTimer,當(dāng)你創(chuàng)建使用NSTimer的時候,NSTimer會默認(rèn)對當(dāng)前self有個強引用,所有在self使用完成打算是否的時候,一定要先使用NSTimer的invalidate來停止是否時間控制對self的引用
[_timer invalidate];
出現(xiàn)循環(huán)引用的三種情況:
1.聲明代理delegate屬性
2.使用block時
3.使用NSTimer的時候
1.代理屬性導(dǎo)致循環(huán)引用。
解決方案就是一定要記住,在聲明delegate的時候修飾為weak(ARC)或者assign(MRC)
ARC環(huán)境下
//一代理屬性為什么用weak,如果用strong的話會發(fā)生循環(huán)引用
//self -- person -- delegate -- self
// self.person = [[Person alloc] init];
// self.person.delegate = self;
MRC環(huán)境同理。
2.block導(dǎo)致的循環(huán)引用
這個比較復(fù)雜,我將它單獨寫在了一篇博文中.
block導(dǎo)致的循環(huán)引用問題的分析基解決辦法
3.關(guān)于NSTimer導(dǎo)致的循環(huán)引用,我暫時不做講解,準(zhǔn)備充分時再補上。
NSTimer經(jīng)常被作為某個類的成員變量,而NSTimer初始化時要制定self為target,容易造成循環(huán)引用。另一方面,若timer一直處于validate的狀態(tài),則其引用計數(shù)將始終大于0.