GCD 的 Timer 相对于 NSTimer 更加灵活、高效(无需在主线程和后台线程之间进行切换),而且 NSTimer 需要在合适的地方调用 invalid 以避免内存泄漏,所以平时比较常用。但是发现 GCD Timer 随便调用 API 的话也会造成内存泄漏。
GCD Timer 通过 Dispatch Source 实现,调用方法如:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW,
2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^() {
// do something
});
dispatch_resume(timer);
如果需要暂停定时器调用
dispatch_suspend(timer);
dispatch_suspend 和 dispatch_resume 应该是成对出现的。两者分别会减少和增加 dispatch 对象的挂起计数,但是没有 API 获取当前是挂起还是执行状态,所以需要自己记录。
dispatch_suspend 暂停队列并不意味着当前执行的 block 暂停
当暂停派发队列时需要注意,调用 dispatch_suspend 暂停一个队列,并不意味着暂停当前正在执行的 block,而是 block 可以执行完,但是接下来的 block 会被暂停,直到 dispatch_resume 被调用。
如果添加 dispatch_suspend 的调用后 timer 是无法被释放的。一般情况下会发生崩溃并报“EXC_BAD_INSTRUCTION”错误,看下 GCD 源码 Source 释放的处理 dispatch source release 的时候判断了当前是否是在暂停状态。
void _dispatch_source_xref_release(dispatch_source_t ds) {
if (slowpath(DISPATCH_OBJECT_SUSPENDED(ds))) {
// Arguments for and against this assert are within 6705399
DISPATCH_CLIENT_CRASH("Release of a suspended object");
}
_dispatch_wakeup(ds);
_dispatch_release(ds);
}
dispatch_suspend 状态下无法释放
但是还有不一般的情况,如果暂停的代码加到 dispatch_source_set_event_handler 的 block 中,并不会发生崩溃,但是这个时候页面会无法释放造成内存泄漏!内存泄漏!内存泄漏!。
dispatch_source_set_event_handler(timer, ^() {
// do something
dispatch_suspend(timer);
});
Demo 代码点击这里,Demo 中进入“GCD Timer” 页面再退出,通过 Allocations 工具可以发现 GCDTimerViewController 没有释放,dealloc 无法执行。
怎么破?如果有需求按照上面这种方式写的话,添加个变量记录下 dispatch_suspend 是否被调用的状态,在 dealloc 时判断下,调用过 dispatch_suspend 则再调用下 dispatch_resume。或者暂停后不需要重新运行 timer 的话,取消 timer,一旦取消则不能再重新运行 timer,只能重建。
dispatch_source_cancel(timer);