#import "TestViewController.h"
@interface TestViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementationTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), (uint64_t)(3*NSEC_PER_SEC), 0);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"dispatch");
});
dispatch_resume(self.timer);
}
- (void)dealloc{
dispatch_source_cancel(_timer);
}
@end
dispatch_source_create(,,,)
用于返回一个dispatch_source_t对象。第一个参数为源类型,最后一个参数为资源要加入的队列。中间两个参数没明白是干什么用的😭,所以都默认0。
dispatch_source_set_timer(,,,)
用来设置我们timer的相关信息。第一个参数是我们的timer对象,第二个是首次触发的延迟时间,第三个参数是触发的时间间隔,最后一个参数是触发的允许延迟值。其中第二个参数,当我们使用dispatch_time或者DISPATCH_TIME_NOW时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用dispatch_walltime可以让计时器按照真实时间间隔进行计时。
dispatch_source_set_event_handler(,)
用来设置timer的触发事件。第一个参数为timer对象,第二个为回调block。dispatch_source_set_event_handler()中的任务实在子线程中执行的,若需要回到主线程,要调用dispatch_async(dispatch_get_main_queue(), ^{}.
dispatch_resume()
激活源对象。新创建的timer必须激活才会执行。
dispatch_suspend()
暂停源对象。dispatch_suspend 暂停队列并不意味着当前执行的 block 暂停,调用 dispatch_suspend 暂停一个队列,并不意味着暂停当前正在执行的 block,而是 block 可以执行完,但是接下来的 block 会被暂停,直到 dispatch_resume 被调用。
dispatch_source_cancel()
销毁定时器。dispatch_source_cancel 则是真正意义上的取消 timer。被取消之后如果想再次执行 timer,只能重新创建新的 timer。这个过程类似于对 NSTimer 执行 invalidate。
需要注意的是,dispatch_source_t 一定要被设置为成员变量,否则将会立即被释放。
注意:暂停(dispatch_suspend)的timer,不能被释放的,会引起崩溃。
Calls to dispatch_suspend() must be balanced with calls to dispatch_resume().
dispatch_suspend 和 dispatch_resume 应该是成对出现的。两者分别会减少和增加 dispatch 对象的暂停计数,但是没有 API 获取当前是暂停还是执行状态,所以需要自己记录。你调用了suspend(暂停)几次,你想resume(恢复)的话,就必须要remuse(恢复)几次,才能继续运行。
所以建议控制器添加一个标识符,记录源是否处于暂停状态,在dealloc事件中判断当前源是否被暂停,如果被暂停,则resume,即可解决内存泄漏问题。
但是还有不一般的情况,如果暂停的代码加到 dispatch_source_set_event_handler 的 block 中,并不会发生崩溃,但是这个时候页面会无法释放造成内存泄漏。
dispatch_source_set_event_handler(timer, ^() {
dispatch_suspend(self.timer);
});
其他注意事项:
1.但remuse(恢复)的状态下,如果再进行一次resume(恢复)就会crash,所以要注册一个BOOL值的状态进行记录,防止多次suspend和resume引起闪退。
2.在suspend(暂停)的状态下,如果你设置timer = nil就会crash。
3.在suspend(暂停)的状态下,即使调用dispatch_source_cancel也没用,会造成内存泄漏,甚至崩溃。
后记:
GCDTimer的优势:不受当前runloopMode的影响。
劣势:其计时效应仍不是百分之百准确的。另外,他的触发事件也有可能被阻塞,当GCD内部管理的所有线程都被占用时,其触发事件将被延迟。
网友评论