我想知道为什么当你在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");
}
答案 0 :(得分:59)
NSTimers安排在当前主题的run loop上。但是,GCD调度线程没有运行循环,因此在GCD块中调度定时器不会做任何事情。
有三种合理的选择:
+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]
创建计时器,然后-[NSRunLoop addTimer:forMode:]
在您想要使用的运行循环上实际安排它。这需要在有问题的运行循环上设置句柄,但如果你想在主线程上执行它,你可以使用+[NSRunLoop mainRunLoop]
。dispatch_async()
返回主队列。这相当于使用主运行循环的选项#1(因为它也将在主线程上创建计时器)。当然,这里真正的问题是,为什么要从GCD队列开始创建一个计时器?
答案 1 :(得分:0)
NSTimer被安排为线程的runloop。在问题代码中,GCD调度的线程的runloop没有运行。您必须手动启动它并且必须有一种退出运行循环的方法,因此您应该保留对NSTimer的引用,并在适当的时间使其无效。
NSTimer对目标有强烈的引用,因此目标不能强烈引用计时器,而runloop对计时器有强烈的引用。
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。有用。那么为什么GST中的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 :(得分:-1)
这是糟糕的想法。我准备删除这个答案,但我把它留在这里是为了避免其他人犯同样的错误。 感谢#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]
});
}
由于您的队列myQueue
包含NSThread且它包含NSRunLoop,并且由于dispatch_async
中的代码运行该NSThread的上下文,currentRunLoop
将返回已停止的运行循环与队列的线程相关联。