三種類型循環(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;
}