计时方式

1、NsTimer方式
会存在延迟情况。此种方法把timer加入runloop中执行。如果runloop正在执行连续性任务,则需要等待任务结束

 __weak typeof(self)wSelf = self;
 self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        wSelf.countNum ++;
        NSLog(@"count is ...%d",wSelf.countNum);
    }];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

子线程启动timer可以参考下这篇文章说的不错。。
https://www.jianshu.com/p/d4589134358a

2、dispatch方式
dispatch_source_t精度很高,系统自动触发,系统级别源

__block int timeout=120; //倒计时120秒
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(_timer, ^{
        if(timeout<=0){ //倒计时结束,关闭
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
        //设置界面的按钮显示 根据自己需求设置
                 
            });
        }else{
            int minutes = timeout / 60;
            int seconds = timeout % 60;
            NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒",minutes, seconds];
            dispatch_async(dispatch_get_main_queue(), ^{
                //设置界面的按钮显示 根据自己需求设置
                NSLog(@"countingTime:%@",strTime);
            });
            timeout—;
     
        }
    });
    dispatch_resume(_timer);

3、CADisplayLink方式(会和timer这样调用方式一样,存在循环引用)
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。

 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerMethod)];
    [_displayLink setPreferredFramesPerSecond:1];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    //销毁定时器
    //[_displayLink invalidate];
    //_displayLink = nil;

相比nstimer来说
原理不同
CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。
时间设定不同
iOS设备的屏幕刷新频率(FPS)是60Hz,因此CADisplayLink的selector默认调用周期是每秒60次,这个周期可以通过frameInterval属性设置,CADisplayLink的selector每秒调用次数=60/frameInterval。比如当frameInterval设为2,每秒调用就变成30次。因此,CADisplayLink周期的设置方式略显不便。(frameInterval已过期。改为了setPreferredFramesPerSecond直接=selector每秒调用次数。。)
使用上来说
CADisplayLink使用场合相对专一,适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。

引发问题
nstimer的循环引用
首先定义新页面,页面内创建变量timer 同时delloc方法内部销毁timer并置空。。

@property(nonatomic,strong) NSTimer * timer;/**ins*/

- (void)viewDidLoad {
   [super viewDidLoad];
   self.view.backgroundColor = [UIColor purpleColor];
   __weak typeof(self)wSelf = self;
1、block的调用方式不会引发循环引用。。
   self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
       wSelf.countNum ++; 
       NSLog(@"count is ...%d",wSelf.countNum);
   }];
   [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

}

2、此种方法引发循环引用。即便使用weakSelf依旧如此。self本身不会释放。。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];

- (void)dealloc
{
   [self.timer invalidate];
   self.timer = nil;
   NSLog(@"self.timer invalidate && self.timer = nil %s",__func__);
}

- (void)stopTimer{
   self.timer = nil;
   [self.timer invalidate];
}

可见当timer指定target=self的时候,引发了循环引用。此时状态是


image.png

vc持有了timer;
timer内部持有Vc;
runloop持有了timer
所以即便self写成wself也没有作用

解决方案
最好的方式是我们预知某些关闭页面情况下,直接调用stopTimer方法,销毁timer,从runloop中移除timer,比如viewWillDisappear内部。

除此之外,还可以使用代理对象。。

@interface TimerProxy : NSObject
+ (instancetype) proxyWithTarget:(id)target;
@property (weak, nonatomic) id target; //若引用vc类目
@end
@implementation LJProxy
+ (instancetype) proxyWithTarget:(id)target
{
    TimerProxy *proxy = [[TimerProxy alloc] init];
    proxy.target = target;
    return proxy;
}
//因为timer指定了TimerProxy 为代理类目,所以势必会到此种寻找调用的selector。。
所以这里直接交由原来类处理,找到其内部调用的方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
@end

此时关系如图所示:


image.png

此时vc没有强引用。。直接pop没有问题。。会执行timer的销毁方法从而全部释放。。。

除了自己的代理类目,还可以直接用系统代理NSProxy
修改代码直接继承自NSProxy

@interface TimerProxy: NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end 
@implementation TimerProxy
+ (instancetype)proxyWithTarget:(id)target {
    TimerProxy*proxy = [TimerProxyalloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

我们看到实现方法不同。。这里需要介绍下nsproxy
runtime学习过程中,查找一个方法。需要直接通过缓存、方法列表各种查找。然后走消息转发。。调用resolveInstanceMethod、forwardTarget、methodSignatureForSelector、forwardInvocatio等一系列处理。。。但是nsproxy会直接的走到methodSignatureForSelector、forwardInvocatio来处理消息转发。。更加的效率高效。。。

文章参考
https://www.jianshu.com/p/a5353deac41b
https://www.jianshu.com/p/69e90ce06475
https://www.jianshu.com/p/d4589134358a

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • NSTimer 是系统提供的定时器,系统提供的api也比较简单,使用很方便,项目开发中会经常用到。然而,在使用NS...
    恋空K阅读 4,037评论 0 13
  • 定时器的用法 系统提供了8个创建方法,6个类创建方法,2个实例初始化方法。有三个方法直接将timer添加到...
    gpylove阅读 1,837评论 1 3
  • NSTimer时钟事件: 一般情况 我们会这样撸 [NSTimer scheduledTimerWithTimeI...
    NextStepPeng阅读 526评论 0 0
  • 我们常用NSTimer的方式 如下代码所示,是我们最常见的使用timer的方式 当使用NSTimer的schedu...
    yohunl阅读 1,700评论 1 17
  • 一、基础篇 1.RunLoop是什么 RunLoop字面意思是跑圈,实际就是运行循环(即死循环)其实它内部就是do...
    irenb阅读 323评论 0 1