NSTimer使用注意总结

使用

下面两个定时器的使用是等价的

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更加高效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值