使用
下面两个定时器的使用是等价的
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
当使用NSTimer的scheduledTimerWithTimeInterval方法时。事实上此时Timer会被加入到当前线程的Run Loop中,且模式是默认的NSDefaultRunLoopMode。
坑点:模式的切换导致的NSTimer出现不准确
比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行。
坑点:时间超过了定时器的间隔时间,导致的NSTimer出现不准确
复杂的运算使当前RunLoop持续的时间超过了定时器的间隔时间,那么下一次定时就被延后,这样就会造成timer的阻塞
解决方案:
方案1:设置Mode为NSRunLoopCommonModes
// 方法1
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
方案2:子线程创建定时器
// 方法2
//子线程中创建定时器(不占用主线程资源)
//注意点:子线程创建定时器需要手动添加到runloop
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
方案3:GCD定时器
@property (nonatomic ,strong)dispatch_source_t timer;// 注意:此处应该使用强引用 strong
{
//0.创建队列
dispatch_queue_t queue = dispatch_get_main_queue();
//1.创建GCD中的定时器
/*
第一个参数:创建source的类型 DISPATCH_SOURCE_TYPE_TIMER:定时器
第二个参数:0
第三个参数:0
第四个参数:队列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//2.设置时间等
/*
第一个参数:定时器对象
第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时
第三个参数:间隔时间 GCD里面的时间最小单位为 纳秒
第四个参数:精准度(表示允许的误差,0表示绝对精准)
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.要调用的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD-----%@",[NSThread currentThread]);
});
//4.开始执行
dispatch_resume(timer);
//
self.timer = timer;
}
-(void) pauseTimer{
if(self.timer){
dispatch_suspend(_timer);
}
}
-(void) resumeTimer{
if(self.timer){
dispatch_resume(_timer);
}
}
-(void) stopTimer{
if(self.timer){
dispatch_source_cancel(_timer);
_timer = nil;
}
}
注意
一定要强引用定时器 ,否则定时器执行过代码段后将会被释放,无定时效果。GCD定时器时间非常精准,最小的定时时间可以达到1纳秒,所以用在非常精确的定时场合。
方案4:CADisplayLink
//CADisplayLink是一个和屏幕刷新率同步的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次,所以可以使用CADisplayLink做一些和屏幕操作相关的操作
//创建定时器
-(void)createCADDisplayTimer {
self.displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
[self.displaylink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
//响应事件
-(void)test {
NSLog(@"执行了CADisplayLink定时器方法");
}
//暂停
-(void)paseCADDisplay {
NSLog(@"暂停CADisplayLink");
[self.displaylink invalidate];
self.displaylink = nil;
}
坑点:子线程创建和销毁
NSTimer的销毁和创建必须在同一个线程上操作
解决方案:
为了直接获取到线程对象,建议用多线程NSThread对象操作
//创建子线程timerThread
-(void)createSynTimer {
self.timerThread = [[NSThread alloc]initWithTarget:self selector:@selector(createSynTimerAction) object:nil];
[self.timerThread start];
}
//创建NStimer
-(void)createSynTimerAction {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 block:^(NSTimer * _Nonnull timer) {
NSLog(@"子线程%@创建的NStimer",[NSThread currentThread]);
} repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
//销毁Nstimer
-(void)destoryThread {
[self performSelector:@selector(destoryTimer) onThread:self.timerThread withObject:nil waitUntilDone:NO];
}
-(void)destoryTimer {
NSLog(@"销毁子线程%@创建的Nstimer",[NSThread currentThread]);
[self.timer invalidate];
self.timer = nil;
}
NSTimer循环引用问题
场景:页面A中使用了NSTimer,页面Apop之后,页面A没有触发dealloc方法
坑点:__weak 不能解除循环引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: weakSelf selector:@selector(test) userInfo:nil repeats:YES];
上面的代码__weak 不能解除循环引用
解决方案
方案1:创建Nstimer的分类,[block copy]作为UserInfo的参数
//方案一 创建Nstimer的分类,[block copy]作为UserInfo的参数
-(NSTimer *)createNStimer {
__weak typeof(self) weakSelf = self;
NSTimer *timer = [NSTimer bf_scheduledTimerWithTimeInterval:1 repeats:YES block:^{
weakSelf.count2--;
NSLog(@"获取计时返回的内容为--%ld",weakSelf.count2);
}];
return timer ;
}
创建 NSTimer分类
@implementation NSTimer (BlockSupport)
+(NSTimer *)bf_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block {
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(timerSelector:) userInfo:[block copy] repeats:repeats];
}
+(void)timerSelector:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
方案2: 声明Block的NSTimer
//方案二 声明Block
-(NSTimer *)createBlocktimer {
__weak typeof(self) weakSelf = self;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
weakSelf.count2--;
NSLog(@"Block获取计时返回的内容为--%ld",weakSelf.count2);
}];
return timer;
}
方案三 使用中间弱引用
WeakTimer.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WeakTimer : NSObject
+(instancetype)timerWithTarget:(id)target;
@property (nonatomic,weak) id target;
@end
NS_ASSUME_NONNULL_END
WeakTimer.m
#import "WeakTimer.h"
@implementation WeakTimer
+ (instancetype)timerWithTarget:(id)target {
WeakTimer *proxy = [[WeakTimer alloc]init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
//方案三 使用中间弱引用类(WeakTimer)
-(NSTimer *)proxyTimer {
NSTimer *proxyTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[WeakTimer timerWithTarget:self] selector:@selector(weakTimerProxy:) userInfo:nil repeats:YES];
return proxyTimer;
}
-(void)weakTimerProxy:(NSTimer *)timer {
self.count2--;
NSLog(@"中间proxy获取计时返回的内容为--%ld",self.count2);
}
方案4: 中间 NSProxy
TimerProxy.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TimerProxy : NSProxy
+(instancetype)proxyWithTarget:(id)target;
@property(nonatomic,weak)id target;
@end
NS_ASSUME_NONNULL_END
TimerProxy.m
#import "TimerProxy.h"
@implementation TimerProxy
+(instancetype)proxyWithTarget:(id)target {
TimerProxy *proxy = [TimerProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
//方案四 中间Proxy
-(NSTimer *)proxytimer2 {
NSTimer *proxyTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TimerProxy proxyWithTarget:self] selector:@selector(weakTimerProxy:) userInfo:nil repeats:YES];
return proxyTimer;
}
方案4更优于方案3。因为方法3会触发消息发送、查找、消息转发的流程,方案4更加高效。
网友评论