iOS开发 计时器的实现方式

iOS中计时器常用的有两种方式

使用NSTimer类(Swift 中更名为 Timer)

NSTimer 常用的初始化方法区别

方法一

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
class func scheduledTimer(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer

  • 创建一个创建一个NSTimer对象,并添加到当前的RunLoop中,使用默认模式RunLoop.current.add(timer, forMode: .defaultRunLoopMode),不需要手动fire,系统会自动执行绑定的aSelector
  • 如果设置了repeatstrue,每隔一定的时间会重复执行aSelector,直到手动调用invalidate方法。repeatsfalse,执行一次之后系统会自动调用invalidate
方法二

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
init(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool)

  • 创建一个创建一个NSTimer对象,需要手动添加到RunLoop中,手动添加到RunLoop中之后开始计时,执行aSelector
  • 如果设置了repeatstrue,每隔一定的时间会重复执行aSelector,直到手动调用invalidate方法。repeatsfalse,执行一次之后系统会自动调用invalidate
方法三 需要iOS 10以上

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

  • 这个方法和方法二一样,使用block回调。block中自带了NSTimer参数,避免循环引用
方法四 需要iOS 10以上

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer

  • 这个方法和方法一一样,使用block回调。block中自带了NSTimer参数,避免循环引用
注释
  • timer 对象中都是强引用。targetuserInfo,添加到RunLoop中也是强引用timer
  • timer 对象调用invalidate方法后,会从RunLoop中移除引用,timer不会执行对应的方法或者blocktimer会移除RunLoop的引用和userInfotarget的引用,也是为一个方法可以移除timer引用的方法。
  • 官方特别注释
  • 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.大致意思就是计时器在那个线程上创建开始的,就需要在那个线程上调用此方法,不过一般我都是在主线程上创建timer,所以在这也没遇到什么坑
  • fire方法只适合触发重复的计时器,并且不会重置计时器的计时时间。如果不是重复的计时器,触发之后会自动失效,不管计时器时间有没有到
  • 官方注释如下:
  • You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

Timer 内存管理

Timer对参数的引用都是强引用,而且添加到RunLoop中也是强引用。在ViewController中,controller持有timerRunLoop持有timer,一般在dealloc中调用timer.invalidate,但是此时RunLoop仍然持有timer,并不会走dealloc,导致ViewController无法释放。要求不要的可以在viewWillDisappear:中调用timer.invalidate。这也只能在当前页面使用定时器,离开了这个页面就不行了。网上有折中的方法就是ViewController不持有timer,借助单例类来实现,这只能算折中的方法。在下方补充中有一种解决方案。

使用GCD

GCD在swift3以后改动很大,所以分开来说

objc

uint64_t interval = 5 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//设置回调

__weak __typeof(self) ws = self
dispatch_source_set_event_handler(_timer, ^(){ [ws sendRequest]; });
dispatch_resume(_timer);
dealloc方法
- (void)dealloc
{
if (_timer != NULL) {
dispatch_cancel(_timer);
_timer = NULL;
}
}

  • 使用dispatch_source_create方法生成dispatch_source_t实例,并且设置回调。
  • dispatch_resume(_timer);执行这个方法timer才会开始,默认是suspend暂停、挂起状态
  • dispatch_suspend(_timer);暂停timer,计时器出于暂停状态,重新启用调用dispatch_resume(_timer);

swift

var ti : DispatchSourceTimer!
ti = DispatchSource.makeTimerSource(flags:DispatchSource.TimerFlags.init(rawValue: 0) , queue: nil);
ti.schedule(deadline: DispatchTime.now(), repeating: 2.0)
ti.setEventHandler { [weak self] in self?.timerAction() }
ti.resume()
deinit { if ti != nil { ti.suspend() } }

  • ti.resume()开始计数器,否则不是主动开始
  • ti.suspend()暂停、挂起计时器,重新开始ti.resume()

总结

如果在一个页面里面需要使用定时器,离开页面就停止,个人倾向于使用NSTimer类实现。如果离开页面不能停止还需要继续执行,使用GCD好一点吧,毕竟不会出现内存问题。折中的方法个人是不喜欢的。大家在项目中一定要具体问题具体对待,实现方法有很多,选择自己认为比较好的,没有问题的才是正确的。

补充 2017-12-19

  • 对于NSTimer循环引用的问题可以使用分类解决,代码如下
  • 新建一个NSTimer Schedule分类,实现如下方法。
    + (NSTimer *) timerWithSchedule:(NSTimeInterval)ti block:(void(^)())block userInfo:(id)userInfo repeats:(BOOL)yesOrNo{ NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:2]; if (userInfo) { info[@"userInfo"] = userInfo; } info[@"timerBlock"] = block; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(timerSelector:) userInfo:info repeats:yesOrNo]; return timer; }
    + (void)timerSelector:(NSTimer *)timer { void(^infoBlock)() = timer.userInfo[@"timerBlock"]; if (infoBlock) { infoBlock(); } }
    使用 分类中的初始化方法进行NSTimer的初始化即可,只需避免block参数中的循环引用即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容