21

我想知道为什么当您在 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");
}
4

3 回答 3

62

NSTimers被安排在当前线程的run loop上。但是,GCD 调度线程没有运行循环,因此在 GCD 块中调度计时器不会做任何事情。

有三种合理的选择:

  1. 弄清楚你想在哪个运行循环上安排计时器,并明确地这样做。用于+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]创建计时器,然后-[NSRunLoop addTimer:forMode:]将其实际安排在您要使用的运行循环上。这需要处理有问题的运行循环,但+[NSRunLoop mainRunLoop]如果你想在主线程上执行它,你可以只使用它。
  2. 切换到使用基于计时器的调度源。这在 GCD 感知机制中实现了一个计时器,它将以您希望在您选择的队列上的间隔运行一个块。
  3. 在创建计时器之前显式dispatch_async()返回主队列。这等效于使用主运行循环的选项#1(因为它还将在主线程上创建计时器)。

当然,这里真正的问题是,你为什么要从 GCD 队列中创建一个计时器?

于 2012-05-09T19:39:04.763 回答
0

NSTimer 被调度到线程的runloop。在问题代码中,GCD 调度的线程的 runloop 没有运行。您必须手动启动它,并且必须有退出运行循环的方法,因此您应该保留对 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 的文档以了解 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)
}
于 2018-01-22T12:33:30.610 回答
-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_asyncNSThread 的上下文中运行,因此currentRunLoop将返回与您的队列线程相关联的停止运行循环。

于 2017-06-12T14:53:50.263 回答