用GCD运行重复NSTimer?

时间:2012-05-09 19:36:30

标签: ios objective-c grand-central-dispatch nstimer nsrunloop

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

3 个答案:

答案 0 :(得分:59)

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

有三种合理的选择:

  1. 找出要在其上安排计时器的运行循环,并明确地执行此操作。使用+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]创建计时器,然后-[NSRunLoop addTimer:forMode:]在您想要使用的运行循环上实际安排它。这需要在有问题的运行循环上设置句柄,但如果你想在主线程上执行它,你可以使用+[NSRunLoop mainRunLoop]
  2. 切换到使用基于计时器的dispatch source。这在GCD感知机制中实现了一个计时器,它将以你想要的队列间隔运行一个块。
  3. 在创建计时器之前,显式dispatch_async()返回主队列。这相当于使用主运行循环的选项#1(因为它也将在主线程上创建计时器)。
  4. 当然,这里真正的问题是,为什么要从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将返回已停止的运行循环与队列的线程相关联。