NSTimer用法與循環(huán)引用

首先介紹NSTimer的幾種創(chuàng)建方式

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

常用方法

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

三種方法的區(qū)別是:

  • scheduledTimerWithTimeInterval方法不僅創(chuàng)建了NSTimer對(duì)象,還把該NSTimer對(duì)象加入到了當(dāng)前的RunLoop(默認(rèn)NSDefaultRunLoopModel模式)中冰更。
  • 前兩個(gè)方法需要使用addTimer:forMode:方法將NSTimer加入到RunLoop中。
- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;

與UIScrollView使用時(shí)注意事項(xiàng)

在當(dāng)前線(xiàn)程為主線(xiàn)程時(shí)扯再,某些UI事件煌妈,比如UIScrollView的拖動(dòng)操作,會(huì)將RunLoop切換為NSEventTrackingRunLoopModel模式冯勉,在這個(gè)過(guò)程中萌壳,默認(rèn)的NSDefaultRunLoopModel模式中注冊(cè)的事件是不會(huì)被執(zhí)行的亦镶。
這時(shí)可以將Timer按照NSRunLoopCommonModes模式加入到RunLoop中日月。
通常情況下NSDefaultRunLoopMode和UITrackingRunLoopMode都已經(jīng)被加入到了common modes集合中, 所以不論runloop運(yùn)行在哪種mode下, NSTimer都會(huì)被及時(shí)觸發(fā)

如何銷(xiāo)毀NSTimer

invalidate方法的官方介紹:

Stops the timer from ever firing again and requests its removal from its run loop.
This method is the only way to remove a timer from an NSRunLoopobject. The NSRunLoop
object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

意思是:

  • invalidate方法會(huì)停止計(jì)時(shí)器的再次觸發(fā),并在RunLoop中將其移除染乌。
  • invalidate方法是將NSTimer對(duì)象從RunLoop中移除的唯一方法山孔。
  • 調(diào)用invalidate方法會(huì)刪除RunLoop對(duì)NSTimer的強(qiáng)引用,以及NSTimer對(duì)target和userInfo的強(qiáng)引用荷憋!

那為什么RunLoop會(huì)對(duì)NSTimer強(qiáng)引用呢台颠?

Timers work in conjunction with run loops. Run loops maintain strong references to their timers
( 計(jì)時(shí)器與運(yùn)行循環(huán)一起工作。運(yùn)行循環(huán)維護(hù)對(duì)計(jì)時(shí)器的強(qiáng)引用)

The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
(當(dāng)計(jì)時(shí)器觸發(fā)后勒庄,在調(diào)用invalidated之前會(huì)一直保持對(duì)target的強(qiáng)引用)

以上也解釋了下面要說(shuō)的NSTimer造成循環(huán)引用的原因

循環(huán)引用造成內(nèi)存泄漏

循環(huán)引用示例.png

由上可見(jiàn):NSTimer強(qiáng)引用了self串前,self也強(qiáng)引用了NSTimer,由此造成了循環(huán)引用实蔽,同時(shí)Runloop也強(qiáng)引用NSTimer荡碾。

  • 下面介紹兩種情況下解決循環(huán)引用
  1. 一般情況下直接在vc的viewWillDisappear中調(diào)用以下方法即可解決
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}
  1. 在A--push--B,B返回A

這種情況顯然是在dealloc中調(diào)用invalidate方法局装,
有些人可能會(huì)想將NSTimer弱引用

@property (nonatomic, weak)NSTimer *timer;
  • 但是RunLoop強(qiáng)引用了timer ~坛吁,timer強(qiáng)引用了vc,所以dealloc不會(huì)被調(diào)用铐尚!
  • 或者target傳入weakSelf拨脉,由于在invalidate方法調(diào)用之前,timer一直強(qiáng)引用target宣增,而強(qiáng)引用了弱引用所引用的對(duì)象玫膀,等價(jià)于強(qiáng)引用!

下面介紹幾種成熟的解決方案

一. 使用自定義Category用Block解決
NSTimer+ZHWeakTimer.h

@interface NSTimer (ZHWeakTimer)
+ (NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(void))eventBlock repeats:(BOOL)repeats;
@end
NSTimer+ZHWeakTimer.m

@implementation NSTimer (ZHWeakTimer)

+ (NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(void))eventBlock repeats:(BOOL)repeats
{
    NSTimer *timer = [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(zh_executeTimer:) userInfo:[eventBlock copy] repeats:repeats];
    
    return timer;
}

+ (void)zh_executeTimer:(NSTimer *)timer
{
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

定時(shí)器對(duì)象指定的target是NSTimer類(lèi)對(duì)象是個(gè)單例爹脾,因此計(jì)時(shí)器是否會(huì)保留它都無(wú)所謂帖旨。這么做,循環(huán)引用依然存在灵妨,但是因?yàn)轭?lèi)對(duì)象無(wú)需回收解阅,所以能解決問(wèn)題。

優(yōu)點(diǎn):代碼簡(jiǎn)潔泌霍,邏輯清晰
缺點(diǎn):
1.需要使用weakSelf避免block循環(huán)引用
2.不再使用原生API
3.同時(shí)要為NSTimer何CADisplayLink分別引進(jìn)一個(gè)Category

二. GCD自己實(shí)現(xiàn)Timer

直接用GCD自己實(shí)現(xiàn)一個(gè)定時(shí)器货抄,YYKit直接有一個(gè)現(xiàn)成的類(lèi)YYTimer這里不再贅述。
缺點(diǎn):代價(jià)有點(diǎn)大烹吵,需要自己重新造一個(gè)定時(shí)器碉熄。

三. 代理NSProxy

使用工具類(lèi)YYWeakProxy解決NSTimer/CADisplayLink循環(huán)引用問(wèn)題桨武!

YYWeakProxy.h
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
-(instancetype)initWithTarget:(id)target;
+(instancetype)proxyWithTarget:(id)target;
@end
YYWeakProxy.m
-(instancetype)initWithTarget:(id)target {
 _target = target;
 return self;
}
+(instancetype)proxyWithTarget:(id)target {
 return [[YYWeakProxy alloc] initWithTarget:target];
}
-(id)forwardingTargetForSelector:(SEL)selector {
 return _target;
}
-(void)forwardInvocation:(NSInvocation *)invocation {
 void *null = NULL;
 [invocation setReturnValue:&null];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
 return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
-(BOOL)respondsToSelector:(SEL)aSelector {
 return [_target respondsToSelector:aSelector];
}
-(BOOL)isEqual:(id)object {
 return [_target isEqual:object];
}
-(NSUInteger)hash {
 return [_target hash];
}
-(Class)superclass {
 return [_target superclass];
}
-(Class)class {
 return [_target class];
}
-(BOOL)isKindOfClass:(Class)aClass {
 return [_target isKindOfClass:aClass];
}
-(BOOL)isMemberOfClass:(Class)aClass {
 return [_target isMemberOfClass:aClass];
}
-(BOOL)conformsToProtocol:(Protocol *)aProtocol {
 return [_target conformsToProtocol:aProtocol];
}
-(BOOL)isProxy {
 return YES;
}
-(NSString *)description {
 return [_target description];
}
-(NSString *)debugDescription {
 return [_target debugDescription];
}
@end

該方法引入一個(gè)YYWeakProxy對(duì)象肋拔,在這個(gè)對(duì)象中弱引用真正的目標(biāo)對(duì)象。通過(guò)YYWeakProxy對(duì)象呀酸,將NSTimer/CADisplayLink對(duì)象弱引用目標(biāo)對(duì)象凉蜂。
使用方法:

 self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:[YYWeakProxy proxyWithTarget:self]
                                                selector:@selector(timeEvent)
                                                userInfo:nil
                                                 repeats:YES];
- (void)timeEvent{
}
- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;// 對(duì)象置nil是一種規(guī)范和習(xí)慣
}
為什么NSProxy的子類(lèi)YYWeakProxy可以解決呢?
  • NSProxy本身是一個(gè)抽象類(lèi),它遵循NSObject協(xié)議窿吩,提供了消息轉(zhuǎn)發(fā)的通用接口茎杂,NSProxy通常用來(lái)實(shí)現(xiàn)消息轉(zhuǎn)發(fā)機(jī)制和惰性初始化資源。不能直接使用NSProxy纫雁。需要?jiǎng)?chuàng)建NSProxy的子類(lèi)煌往,并實(shí)現(xiàn)init以及消息轉(zhuǎn)發(fā)的相關(guān)方法,才可以用轧邪。
  • YYWeakProxy繼承了NSProxy刽脖,定義了一個(gè)弱引用的target對(duì)象,通過(guò)重寫(xiě)消息轉(zhuǎn)發(fā)等關(guān)鍵方法忌愚,讓target對(duì)象去處理接收到的消息曲管。在整個(gè)引用鏈中,Controller對(duì)象強(qiáng)引用NSTimer/CADisplayLink對(duì)象硕糊,NSTimer/CADisplayLink對(duì)象強(qiáng)引用YYWeakProxy對(duì)象院水,而YYWeakProxy對(duì)象弱引用Controller對(duì)象,所以在YYWeakProxy對(duì)象的作用下简十,Controller對(duì)象和NSTimer/CADisplayLink對(duì)象之間并沒(méi)有相互持有檬某,完美解決循環(huán)引用的問(wèn)題。

參考文檔

1.iOS實(shí)錄8:解決NSTimer/CADisplayLink的循環(huán)引用
2.NSTimer Class

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勺远,一起剝皮案震驚了整個(gè)濱河市橙喘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胶逢,老刑警劉巖厅瞎,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異初坠,居然都是意外死亡和簸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)碟刺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锁保,“玉大人,你說(shuō)我怎么就攤上這事半沽∷猓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵者填,是天一觀的道長(zhǎng)浩村。 經(jīng)常有香客問(wèn)我,道長(zhǎng)占哟,這世上最難降的妖魔是什么心墅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任酿矢,我火速辦了婚禮,結(jié)果婚禮上怎燥,老公的妹妹穿的比我還像新娘瘫筐。我一直安慰自己,他們只是感情好铐姚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布策肝。 她就那樣靜靜地躺著,像睡著了一般隐绵。 火紅的嫁衣襯著肌膚如雪驳糯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天氢橙,我揣著相機(jī)與錄音酝枢,去河邊找鬼。 笑死悍手,一個(gè)胖子當(dāng)著我的面吹牛帘睦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坦康,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼竣付,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了滞欠?” 一聲冷哼從身側(cè)響起古胆,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筛璧,沒(méi)想到半個(gè)月后逸绎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夭谤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年棺牧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朗儒。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颊乘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出醉锄,到底是詐尸還是另有隱情乏悄,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布恳不,位于F島的核電站檩小,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏妆够。R本人自食惡果不足惜识啦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望神妹。 院中可真熱鬧颓哮,春花似錦、人聲如沸鸵荠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛹找。三九已至姨伤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庸疾,已是汗流浹背乍楚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留届慈,地道東北人徒溪。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像金顿,于是被迫代替她去往敵國(guó)和親臊泌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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