关于NSTimer
在工作中经常会做一些延时任务,或者周期性任务,有时候也需要对取消延时任务操作。
延时任务一般有三种
- NSObject的
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- 使用NSTimer的一些函数
+(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的一些函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
> 如果这个任务不需要撤销,使用GCD是最好的选择
如果需要有撤销操作,就不能使用GCD,使用NSObject的函数或者使用NSTimer需要注意一些细节
* 必须有一个活跃的RunLoop, performSelector和NSTimer的一些函数都是基于RunLoop的,主线程的RunLopp是默认启动的,如果放在子线程创建必须手动启动子线程的RunLoop。
* NSTimer的创建和撤销需要放在同一个线程操作,performSelector的创建与撤销必须在同一个线程操作。
* 有内存泄露的潜在风险,scheduledTimerWithTimeInterval函数会对target进行强引用,而NSTimer会被当前的runloop强引用,只有当调用NSTimer的invalidate才能解除掉这个强引用,performSelector也同样会对target进行强引用,必须手动取消才可以解除这个强引用,当我们创建一个NSTimer的时候,我们在当前对象的dealloc函数中invalidate这个timer,看似合理,但是dealloc永远不会被调用,造成了内存泄露
> 看下苹果对NSTimer的invalidate函数介绍
> 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.
只有这个函数才能从runloop中删除这个定时器。
>
所以我们使用NSTimer会考虑timer何时释放的问题,我们一般的做法是在页面显示的时候启动这个定时器,页面消失的时候停止这个定时器
但是有的时候有需求,需要定时器不要停止,我们从当前页面push到另外一个页面的时候 定时器不停止。如果停止定时器放在viewWillDisappear函数中时,push到其他页面定时器一样会停止。
>
保证Timer一定会被停止,可以手动停止,就是调用invalidate,可以在页面退出的时候手动的去invalidate,(在back的时候,等等)。
只前遇到这种需求,对Timer做了一个简单的封装,先看代码
BFSTimerextension.h
import <Foundation/Foundation.h>
@protocol BFSTimerextensionDelegate <NSObject>
/**
定时器调用代理函数
/
-(void)timerfucntionCall;
@end
@interface BFSTimerextension : NSObject
@property(nonatomic,weak) id<BFSTimerextensionDelegate> delegate;
/*
启动一个定时器
@param timeInterval 间隔时间
@param repeats 是否重复
/
-(void)startTimer:(NSTimeInterval)timeInterval repeats:(BOOL)repeats;
/*
停止这个定时器
*/
-(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创建一个Timer timer的tagret是BFSTimerextension,如果BFSTimerextension的delegate被释放,timer直接释放掉,我们不用在考虑timer什么时机停止的问题,
注:文中若有错误,请指出,谢谢
代码地址请点击[这里](https://github.com/CharType/Timerextension)