彭序猿

耕读传家,学为好人

你好,我是彭序猿,目前在iOS开发的道路上探索,这里会写点关于iOS开发blog,还有一些生活上的琐碎事儿。


我们做技术的,不会耍什么心眼,从来都是你看我顺眼,我看你顺眼,那我们就是好基友~

NSTimer 知识点

[TOC]

NSTimer 是什么

定时器 一般都是用来做一些周期性的任务

使用遇到什么问题

内存释放问题、定时器失效问题

为什么会出现这些问题

内存释放问题

当定时器被加到run loop 生效的时候,run loop 会强引用这个定时器对象(retain),然后定时器又会强引用这个Target 对象,这样子就会导致这个定时器一直存在,这个Target 对象一直存在,导致一直释放不了

单纯将NSTimer置为nil,是不能使定时器失效的,runloop 已经强引用这个timer 了,要使得定时器失效需要调用invalidate 方法

定时器失效问题 (场景:滑动TableView 的时候,定时器失效)

run loop 里面有很多模式,但是一个时间点只会处在一个模式下,在某个模式的时候,只会处理和调度这个模式下面的事件和资源,NSTimer 在用scheduledTimerWithTimeInterval 生成的时候是以默认模式加入到当前的run loop,然后模式是NSDefaultRunLoopMode,但是当TableView 滑动的时run loop 会切换到UITrackingRunLoopMode 这个模式,所以此时NSTimer 就会失效

所以这里是:当run loop 切换到不是timer 加入run loop 时的模式时,这个定时器就会暂时失效

关于手动将timer 置为nil 的意义

一个变量,如果被 strong 修饰,和被 weak 修饰,是有一定的区别的。

  • strong

若一个变量被 strong 修饰 (临时变量或者非 property 变量默认 strong 修饰),则其为强指针指向,在被赋值的时候,会强引用赋值对象。

  • weak

若一个变量被 weak 修饰,则其为弱指针指向,弱指针指向的对象,如果没有被外界进行强引用的话,会在初始化方法完成之后,将变量置空。

在 timer 被 strong 和 weak 修饰时的区别

  1. 如果 timer 被 strong 修饰,那么 timer 会被 self 持有一次,加入 runloop 之后,会被 runloop 再持有一次,invalidate 只会解除 runloop 对 timer 的持有,self 还是会 持有 timer ,所以需要手动置为 nil ,解除 self 对 timer 的持有。
  2. 如果 timer 被 weak 修饰,那么 timer 不会被 self 持有,只是 runloop 对 timer 有一次持有(==前提是外界没有持有该timer,如果外界持有了,需要外界也手动置空==),invalidate 解除了 runloop 对 timer 的持有之后,系统会自动将 timer 置空。

如果我们用了懒加载,而且 timer 是weak 修饰,没有初始化过,在下面这种写法的时候,

[self.timer invalidate];
NSLog(@"%@", _timer);
[self.timer fire];

会发现,打印出来的 _timer 依然有值。

原因是:因为 timer 并没有被初始化,我们在调用 self.timer 的时候,会走get方法,因为我们在 get 方法里做了懒加载,就会走一遍初始化,但是我们又在初始化方法里,加进了 runloop ,runloop 强引用了 timer ,那么在 get 方法执行完之后,timer 并不会被销毁。但是,我们又调用了 invalidate ,这个操作只会解除 runloop 对 timer 的持有,引用计数 -1,却并不会立即将 timer 置为 nil ,所以在 调用打 log 和 [self.timer fier] 的时候,get 方法并不会走初始化操作了,但是因为 timer 已经不被 runloop 强引用了,所以会失效。

关于调用invalidate 方式

在调用invalidate 是否需要用valid 来判断,懒加载的情况是需要的,因为会被置为nil,多次调用的时候会重新创建,但是直接会被invalidate,这里定位会比较麻烦 invalidate 这个是使定时器失效的,调用的时候不能dealloc 里面去调用,因为run loop 强引用了定时器,定时器又强引用了Target 对象,这个时候VC 是不会被释放的,自然不会走dealloc 方法

关于NSTimer 暂停开始

这里最优雅的方式就是写一个NSTimer 的分类,然后用fireDate 触发时间来处理

如何正确使用

#pragma mark - Lazzy load
- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerStep) userInfo:nil repeats:YES];
    }
    return _timer;
}

-(void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    if ([self.timer isValid]) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

NSTimer 官方文档介绍

NSTimer

你可以用NSTimer 这个类来创建定时器对象。定时器会等待一个时间间隔然后触发,向一个目标对象(Target)发送特定的消息。例如你可以创建一个定时器对象,发消息到window,告诉它隔一段时间就更新自己

Overview

定时器跟run loop 一起工作,要有效地使用定时器,你应该注意run loop 的运行机制 — 可以参阅NSRunLoop 和Threading Programming Guide 章节。特别注意,run loop 会对run loop 中的定时器维持强引用,所以你不需要在把这个定时器加入到run loop 后对它维持强引用

定时器并不是一个实时机制;只有run loop 正在运行的模式是定时器加到run loop 的模式时候并且检查到这个定时器的触发时间是否过去,这样子定时器才会触发。(run loop 有很多种模式,但是一个时间点只会处在一个模式下,然后定时器加入到run loop 也是可以设置需要加入到run loop 什么模式下的)因为有存在各种输入源,run loop 是典型的循环运行管理,所以定时器间隔的有效分辨是在50-100毫秒量级。如果定时器要触发的时候出现了一个耗时的操作或者当前run loop 模式不是定时器加入时候的模式,这样子定时器是不会被触发的直到下次run loop 检查定时器。

因此,定时器的触发的实际时间有可能是预定触发时间之后的相当长的一段时间,另请参考Timer Tolerance章节

NSTimer 跟 Core Foundation的CFRunLoopTimerRef是“toll-free bridge”,有关toll-free bridging 信息请参考Toll-free Bridging 章节

Repeating Versus Non-Repeating Timers (重复与不重复定时器)

你在创建定时器可以指定是重复的还是不重复的。不重复定时器触发一次,然后会自动的使其自动失效,从而防止定时器再次触发。相比之下,重复定时器会在相同的run loop 上面触发并且重新排列

重复定时器总是根据预定的触发时间自动安排,而不是根据实际的触发时间。例如,如果定时器计划在一个特定的时间点和之后的每5秒触发, 尽管实际的触发时间延迟了,预定的触发时间依然是在原始时间点的之后5秒,如果触发时间被延迟到它下一个(或者下几个)触发时间,则这个时间段定时器只会触发一次,然后定时器在触发后会重新排列,等待下一个预定的触发时间

Timer Tolerance 定时器误差

在iOS 7及更高版本以及macOS 10.9及更高版本中,你可以指定定时器的的误差。误差可以使得在定时器触发的时候系统更加灵活,可以提高系统的优化能力,从而提高功率节省和响应能力。 定时器会在预定的触发时间点和预定触发时间点加上误差时间之间触发(A<->A+误差)。计时器不会在预定触发时间点之前触发。对于重复定时器,下一个触发时间是从原始时间点开始计算的,不会因为个别触发时间的误差来重新计算,避免时间点偏移。误差值默认是0,这就意味着不会加上误差,但是系统保留对某个定时器触发加上一个很小的误差的权利,这个时候不会考虑这个误差属性

使用定时器,你最好了解一个合适的定时器误差值。一般的经验法则是将这个误差值最少设置成定时器间隔的10%,尽管是一个很小的误差值,也会对应用程序的耗电产生很大的积极影响。

Scheduling Timers in Run Loops

一个定时器对象只能在run loop 中注册一次,尽管它可以被加到run loop 中的不同的run loop模式中去,这里有3中方法创建定时器:

Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target : selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.

用这两个类方法创建定时器对象,会将这个定时器对象以默认run loop 模式(NSDefaultRunLoopMode)加到当前的run loop 中

Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval`:targetselector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)

用这个两个类方法创建定时器对象,不会将这个对象加入到run loop 中,在创建对象后,你必须手动的将这个定时器对象加入到一个run loop 中,[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

Allocate the timer and initialize it using the initWithFireDate:interval:target :selector:userInfo:repeats: method. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.) 在创建对象后,你必须手动的将这个定时器对象加入到一个run loop 中,[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

一旦添加到run loop中,定时器会在指定的时间间隔触发直到这个定时器失效(invalidate)。一个不重复的定时器在触发后立即使其自动失效。但是对于重复的定时器调用invalidate 才能使它失效。调用invalidate 这个方法会从当前的run loop 中移除这个定时器。因此你应该在创建这个定时器的线程调用这个invalidate 方法。会立即使这个定时器失效,使其不再影响这个run loop。在invalidate 方法返回前后,这个run loop 会移除这个定时器和对这个定时器的强引用。一旦失效,这个定时器对象不能被重用

在重复定时器触发之后,它会安排下一次预定时间点的触发,这个时间点是上一次触发时间点间隔的整数倍,在公差范围内。如果调用选择器方法或者调用NSInvocation 花费的时间长于定时器的间隔时间,则这个定时器会安排在下一次再触发,也就是说,定时器不会尝试补偿在调用选择器方法或调用NSInvocation 过久导致错过启动时间。

Subclassing Notes

你不应该尝试去创建NSTimer 的子类 Crash

Symbols

Relationships

Inherits From NSObject

最近的文章

iOS Security 安全白皮书(一)

[TOC]前言iOS 安全白皮书是苹果官方提供的,里面有苹果对于安全设计的一些细节介绍,阅读可以更佳理解苹果系统的构造,虽然不能说是看了能很深的明白其设计思路,但是可以增加知识,为以后做安全方面打下基础官方原文地址 这个是官方的文档,英文好的同学强烈建议看原文档,个人经验来说,看英文跟看中文完全是两种不同的体验,但是看英文会更加有趣更加深刻17年白皮书翻译,我是看到巧神推荐才知道有iOS 安全白皮书的,以前对于白皮书的概念还是高中的练习辅导呢,那时候上网找了下有没中文文档,很遗憾没有找到...…

继续阅读
更早的文章

Effective Objective-C 2.0 总结(一)

前言阅读书籍全名Effective Objective-C 2.0 编写高质量iOS与OS X 代码的52个有效方法最经买了本编写高质量代码 改善Objective-C程序的61个建议,拿到手看了下目录感觉内容比这本52个有效方法更深点,之前只是浅度这本,具体讲什么也不是很记得了,所以打算先重新看下这本52个有效方法,然后再来拜读新入手的这本。这里准备记录下Effective Objective-C 2.0 编写高质量iOS与OS X 代码的52个有效方法这本提到的知识点。第一章 熟悉Ob...…

继续阅读