關(guān)于NSTimer
在工作中經(jīng)常會做一些延時(shí)任務(wù),或者周期性任務(wù),有時(shí)候也需要對取消延時(shí)任務(wù)操作。
延時(shí)任務(wù)一般有三種
- NSObject的
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- 使用NSTimer的一些函數(shù)
+(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;
- 使用GCD的一些函數(shù)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
> 如果這個(gè)任務(wù)不需要撤銷,使用GCD是最好的選擇
如果需要有撤銷操作,就不能使用GCD,使用NSObject的函數(shù)或者使用NSTimer需要注意一些細(xì)節(jié)
* 必須有一個(gè)活躍的RunLoop, performSelector和NSTimer的一些函數(shù)都是基于RunLoop的,主線程的RunLopp是默認(rèn)啟動的,如果放在子線程創(chuàng)建必須手動啟動子線程的RunLoop。
* NSTimer的創(chuàng)建和撤銷需要放在同一個(gè)線程操作,performSelector的創(chuàng)建與撤銷必須在同一個(gè)線程操作。
* 有內(nèi)存泄露的潛在風(fēng)險(xiǎn),scheduledTimerWithTimeInterval函數(shù)會對target進(jìn)行強(qiáng)引用,而NSTimer會被當(dāng)前的runloop強(qiáng)引用,只有當(dāng)調(diào)用NSTimer的invalidate才能解除掉這個(gè)強(qiáng)引用,performSelector也同樣會對target進(jìn)行強(qiáng)引用,必須手動取消才可以解除這個(gè)強(qiáng)引用,當(dāng)我們創(chuàng)建一個(gè)NSTimer的時(shí)候,我們在當(dāng)前對象的dealloc函數(shù)中invalidate這個(gè)timer,看似合理,但是dealloc永遠(yuǎn)不會被調(diào)用,造成了內(nèi)存泄露
> 看下蘋果對NSTimer的invalidate函數(shù)介紹
> Discussion
>
This method is the only way to remove a timer from an NSRunLoop object. 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.
>
Special Considerations
>
You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
只有這個(gè)函數(shù)才能從runloop中刪除這個(gè)定時(shí)器。
>
所以我們使用NSTimer會考慮timer何時(shí)釋放的問題,我們一般的做法是在頁面顯示的時(shí)候啟動這個(gè)定時(shí)器,頁面消失的時(shí)候停止這個(gè)定時(shí)器
但是有的時(shí)候有需求,需要定時(shí)器不要停止,我們從當(dāng)前頁面push到另外一個(gè)頁面的時(shí)候 定時(shí)器不停止。如果停止定時(shí)器放在viewWillDisappear函數(shù)中時(shí),push到其他頁面定時(shí)器一樣會停止。
>
保證Timer一定會被停止,可以手動停止,就是調(diào)用invalidate,可以在頁面退出的時(shí)候手動的去invalidate,(在back的時(shí)候,等等)。
只前遇到這種需求,對Timer做了一個(gè)簡單的封裝,先看代碼
BFSTimerextension.h
import <Foundation/Foundation.h>
@protocol BFSTimerextensionDelegate <NSObject>
/**
定時(shí)器調(diào)用代理函數(shù)
/
-(void)timerfucntionCall;
@end
@interface BFSTimerextension : NSObject
@property(nonatomic,weak) id<BFSTimerextensionDelegate> delegate;
/*
啟動一個(gè)定時(shí)器
@param timeInterval 間隔時(shí)間
@param repeats 是否重復(fù)
/
-(void)startTimer:(NSTimeInterval)timeInterval repeats:(BOOL)repeats;
/*
停止這個(gè)定時(shí)器
*/
-(void)stopTimer;
@end
BFSTimerextension.m
import "BFSTimerextension.h"
@interface BFSTimerextension()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation BFSTimerextension
-(void)dealloc
{
NSLog(@"被釋放");
}
-(void)startTimer:(NSTimeInterval)timeInterval repeats:(BOOL)repeats
{
if(!_timer)
{
_timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(timerSelectCall) userInfo:nil repeats:repeats];
}
}
-(void)timerSelectCall
{
if(self.delegate && [self.delegate respondsToSelector:@selector(timerfucntionCall)])
{
[self.delegate timerfucntionCall];
}else{
[self.timer invalidate];
self.timer = nil;
}
}
-(void)stopTimer
{
if(self.timer)
{
[self.timer invalidate];
self.timer = nil;
}
}
@end
使用BFSTimerextension創(chuàng)建一個(gè)Timer timer的tagret是BFSTimerextension,如果BFSTimerextension的delegate被釋放,timer直接釋放掉,我們不用在考慮timer什么時(shí)機(jī)停止的問題,
注:文中若有錯(cuò)誤,請指出,謝謝
代碼地址請點(diǎn)擊[這里](https://github.com/CharType/Timerextension)