使用 GCD 运行重复 NSTimer?
Posted
技术标签:
【中文标题】使用 GCD 运行重复 NSTimer?【英文标题】:Run repeating NSTimer with GCD? 【发布时间】:2012-05-18 08:55:41 【问题描述】:我想知道为什么当您在 GCD 块中创建重复计时器时它不起作用?
这很好用:
-(void)viewDidLoad
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
-(void)runTimer
NSLog(@"hi");
但这确实有效:
dispatch_queue_t myQueue;
-(void)viewDidLoad
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
);
-(void)runTimer
NSLog(@"hi");
【问题讨论】:
【参考方案1】:NSTimer 被调度到线程的 runloop。在问题代码中,GCD 调度的线程的运行循环没有运行。您必须手动启动它,并且必须有退出运行循环的方法,因此您应该保留对 NSTimer 的引用,并在适当的时候使其无效。
NSTimer对target有强引用,所以target不能对timer有强引用,runloop对timer有强引用。
weak var weakTimer: Timer?
func configurateTimerInBackgroundThread()
DispatchQueue.global().async
// Pause program execution in Xcode, you will find thread with this name
Thread.current.name = "BackgroundThreadWithTimer"
// This timer is scheduled to current run loop
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
// Start current runloop manually, otherwise NSTimer won't fire.
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
@objc func runTimer()
NSLog("Timer is running in mainThread: \(Thread.isMainThread)")
如果以后定时器失效了,在Xcode中再次暂停程序执行,你会发现那个线程已经没有了。
当然,GCD 调度的线程有runloop。 GCD 在内部生成和重用线程,线程对调用者是匿名的。如果你觉得不安全,你可以使用 Thread.不要害怕,代码很容易。
实际上,我上周尝试了同样的事情并与提问者同样失败,然后我找到了这个页面。在我放弃之前,我尝试了 NSThread。有用。那么为什么 GCD 中的 NSTimer 不能工作呢?它应该是。阅读runloop's document 了解 NSTimer 的工作原理。
使用 NSThread 与 NSTimer 一起工作:
func configurateTimerInBackgroundThread()
let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil)
thread.name = "BackgroundThreadWithTimer"
thread.start()
@objc func addTimerInBackground()
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
【讨论】:
【参考方案2】:这是一个坏主意。我正要删除这个答案,但我把它留在这里是为了避免其他人犯同样的错误。 感谢#Kevin_Ballard 指出这一点。
您只需在示例中添加一行,它就会像您编写的那样工作:
[[NSRunLoop currentRunLoop] run]
所以你会得到:
-(void)viewDidLoad
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run]
);
由于您的队列 @987654323@ 包含一个 NSThread 并且它包含一个 NSRunLoop 并且由于 dispatch_async
中的代码在该 NSThread 的上下文中运行,currentRunLoop
将返回与您的线程相关联的停止运行循环排队。
【讨论】:
这是个坏主意。您现在已经选择了一个调度线程来用作运行循环,并且您永远不会将线程的控制权返回给 GCD。这基本上就像将无限循环放入调度块中。接管调度线程并且从不将它们的控制权返回给 GCD 会损害整个系统的性能,如果你对足够多的线程这样做,你实际上可以让 GCD 完全停止处理提交到非全局队列的块。 你的代码只需要一种退出runloop的方法。保留对 NSTimer 的引用,并在适当的时候使其失效。【参考方案3】:NSTimers 被安排在当前线程的run loop 上。但是,GCD 调度线程没有运行循环,因此在 GCD 块中调度计时器不会做任何事情。
有三种合理的选择:
-
找出您想在哪个运行循环上安排计时器,并明确地这样做。使用
+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]
创建计时器,然后使用-[NSRunLoop addTimer:forMode:]
在您要使用的运行循环上实际安排它。这需要处理相关的运行循环,但如果您想在主线程上处理,您可以只使用+[NSRunLoop mainRunLoop]
。
切换到使用基于计时器的dispatch source。这在 GCD 感知机制中实现了一个计时器,它将以您希望的间隔在您选择的队列上运行一个块。
在创建计时器之前显式地将dispatch_async()
返回到主队列。这相当于使用主运行循环的选项#1(因为它也会在主线程上创建计时器)。
当然,这里真正的问题是,你为什么要从 GCD 队列中创建一个计时器?
【讨论】:
Kevin 的第二个答案之后的另一种选择,我通过用 MSWeakTimer ( github.com/mindsnacks/MSWeakTimer ) 替换 NSTimer 解决了我的问题,并且刚刚通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) 作为 dispatchQueue 。 很棒的总结。超级感谢。如果我理解正确: 1. 你是说没有 GCD 调度线程有一个主线程的运行循环 except 吗? 2. 如果为真,那么 dispatch_after 怎么样?如果没有运行循环,它将如何处理?还是它也是在 GCD 感知机制中实现的? @Honey Dispatch 线程没有运行循环。主线程不是调度线程(好吧,除非你使用dispatch_main()
,如果是这样,它也没有运行循环,但你可能没有这样做)。主线程的 runloop 与 GCD 集成以显式排空主队列,作为 runloop 正常操作的一部分。 dispatch_after
是 GCD 的一部分(因此名称中有“dispatch”一词),它与 runloops 没有任何关系。
主线程是进程开始时唯一存在的线程。当 ios 应用程序调用 UIApplicationMain
(如果您以正常方式设置应用程序委托,它会在后台执行此操作),它会在此线程上启动“主运行循环”。主运行循环一直运行到应用程序终止。主运行循环负责处理许多事情,包括所有 UI 事件,并且您的大部分代码(尤其是所有 UI 代码)都在主线程上运行,由主运行循环驱动。
调度线程是由 GCD 内部管理的线程,用于处理提交到调度队列的块。提交到主队列的块的特殊之处在于主运行循环自己处理它们,但提交到其他队列的块由 GCD 处理并在其中一个调度线程上执行。它们基本上只是工作线程。以上是关于使用 GCD 运行重复 NSTimer?的主要内容,如果未能解决你的问题,请参考以下文章
Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用
GCD dispatch_source基本使用,创建GCD定时器与NSTimer的区别
AVAudioPlayer 本地音频设置番外篇:后台定时修改音频音量NSTimer