如何在后台线程上创buildNSTimer?

我有一个任务需要每1秒执行一次。 目前我有一个NSTimer每1秒重复发射一次。 我如何有后台线程(非UI线程)的计时器火灾?

我可以在主线程上触发NSTimer,然后使用NSBlockOperation来派发一个后台线程,但是我想知道是否有更高效的方法来执行此操作。

计时器需要被安装到一个在已经运行的后台线程上运行的运行循环中。 该线程将不得不继续运行运行循环来让计时器实际触发。 对于后台线程继续触发其他计时器事件,它需要产生一个新的线程来实际处理事件(当然,假设你正在进行的处理花费了大量的时间)。

无论什么价值,我认为通过使用Grand Central Dispatch或NSBlockOperation产生一个新的线程来处理定时器事件是一个完全合理的使用你的主线程。

如果你需要这个,那么当你滚动你的视图(或者地图)时,定时器仍然运行,你需要在不同的运行循环模式下安排它们。 更换你的当前计时器:

 [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; 

有了这个:

 NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 

有关详细信息,请查看此博客文章: 事件跟踪停止NSTimer

编辑:第二块代码,NSTimer仍然在主线程上运行,仍然在滚动视图相同的运行循环。 不同的是运行循环模式 。 检查博客文章以获得清晰的解释。

如果您想要使用纯粹的GCD并使用调度源,Apple在其“ 并发编程指南”中提供了一些示例代码:

 dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); if (timer) { dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); dispatch_source_set_event_handler(timer, block); dispatch_resume(timer); } return timer; } 

Swift 3:

 func createDispatchTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval, queue: DispatchQueue, block: @escaping ()->()) -> DispatchSourceTimer { let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0), queue: queue) timer.scheduleRepeating(deadline: DispatchTime.now(), interval: interval, leeway: leeway) // Use DispatchWorkItem for compatibility with iOS 9. Since iOS 10 you can use DispatchSourceHandler let workItem = DispatchWorkItem(block: block) timer.setEventHandler(handler: workItem) timer.resume() return timer } 

然后,您可以使用如下代码设置您的一秒钟计时器事件:

 dispatch_source_t newTimer = CreateDispatchTimer(1ull * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Repeating task }); 

当然,确保在完成时存储和释放计时器。 如果你愿意的话,你可以在这些事件发生的时候给你1/10秒的回旋余地。

这应该工作,

它在一个背景队列中每1秒重复一次方法,而不使用NSTimers 🙂

 - (void)methodToRepeatEveryOneSecond { // Do your thing here // Call this method again using GCD dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); double delayInSeconds = 1.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, q_background, ^(void){ [self methodToRepeatEveryOneSecond]; }); } 

如果你在主队列中,并且你想要调用上面的方法,你可以这样做,所以在运行之前它会变成背景队列:)

 dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_async(q_background, ^{ [self methodToRepeatEveryOneSecond]; }); 

希望能帮助到你

对于swift 3.0,

吉洪夫的答案没有太多解释。 这里增加了一些我的理解。

为了使事情简短起见,这里是代码。 在创build定时器的地方,Tikhonv的代码是不同的。 我使用constructer创build定时器并将其添加到循环中。 我认为scheduleTimer函数将定时器添加到主线程的RunLoop。 所以最好使用构造函数创build计时器。

 class RunTimer{ let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent) let timer: Timer? private func startTimer() { // schedule timer on background queue.async { [unowned self] in if let _ = self.timer { self.timer?.invalidate() self.timer = nil } let currentRunLoop = RunLoop.current self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true) currentRunLoop.add(self.timer!, forMode: .commonModes) currentRunLoop.run() } } func timerTriggered() { // it will run under queue by default debug() } func debug() { // print out the name of current queue let name = __dispatch_queue_get_label(nil) print(String(cString: name, encoding: .utf8)) } func stopTimer() { queue.sync { [unowned self] in guard let _ = self.timer else { // error, timer already stopped return } self.timer?.invalidate() self.timer = nil } } } 

创build队列

首先,创build一个队列,使计时器在后台运行,并将该队列存储为类属性,以便将其重新用于停止计时器。 我不确定是否需要使用相同的队列来启动和停止,我这样做的原因是因为我在这里看到了一条警告消息。

RunLoop类通常不被认为是线程安全的,只能在当前线程的上下文中调用它的方法。 您不应该尝试调用在不同线程中运行的RunLoop对象的方法,否则可能会导致意外的结果。

所以我决定存储队列,并使用相同的队列来避免同步问题。

也创build一个空的计时器,并存储在类variables中。 使其可选,以便您可以停止计时器并将其设置为零。

 class RunTimer{ let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent) let timer: Timer? } 

启动计时器

要启动计时器,请首先从DispatchQueue调用asynchronous。 那么首先检查计时器是否已经开始是一个好习惯。 如果计时器variables不是零,则将其无效(),并将其设置为零。

下一步是获取当前的RunLoop。 因为我们在创build的队列中执行了这个操作,所以它将获得我们之前创build的背景队列的RunLoop。

创build计时器。 在这里,而不是使用scheduledTimer,我们只是调用定时器的构造函数,并传递给定时器的任何属性,如timeInterval,target,selector等。

将创build的计时器添加到RunLoop。 运行。

这里是关于运行RunLoop的一个问题。 根据这里的文档,它说它有效地开始了一个无限循环,处理来自运行循环的input源和定时器的数据。

 private func startTimer() { // schedule timer on background queue.async { [unowned self] in if let _ = self.timer { self.timer?.invalidate() self.timer = nil } let currentRunLoop = RunLoop.current self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true) currentRunLoop.add(self.timer!, forMode: .commonModes) currentRunLoop.run() } } 

触发器定时器

正常执行该function。 当这个函数被调用时,默认情况下它会在队列下被调用。

 func timerTriggered() { // under queue by default debug() } func debug() { let name = __dispatch_queue_get_label(nil) print(String(cString: name, encoding: .utf8)) } 

上面的debuggingfunction是用来打印出队列的名字。 如果你担心它是否在队列中运行,你可以调用它来检查。

停止计时器

停止定时器很容易,调用validate()并将类中存储的定时器variables设置为nil。

在这里我再次在队列下运行它。 由于这里的警告,我决定在队列下运行所有​​与定时器相关的代码以避免冲突。

 func stopTimer() { queue.sync { [unowned self] in guard let _ = self.timer else { // error, timer already stopped return } self.timer?.invalidate() self.timer = nil } } 

有关RunLoop的问题

我有点困惑,如果我们需要手动停止RunLoop或不。 根据这里的文档,似乎没有定时器附加到它,那么它将立即退出。 所以当我们停止计时器时,它应该存在自己。 但是,在该文件的最后,它还表示:

从运行循环中删除所有已知的input源和定时器并不能保证运行循环将退出。 macOS可以根据需要安装和删除额外的input源,以处理针对接收者线程的请求。 这些来源可以阻止运行循环退出。

我尝试了文档中提供的解决scheme,以保证终止循环。 但是,将.run()更改为下面的代码后,计时器不会启动。

 while (self.timer != nil && currentRunLoop.run(mode: .commonModes, before: Date.distantFuture)) {}; 

我在想,在iOS上使用.run()可能是安全的。 因为文档指出,macOS是安装和删除额外的input源来处理接收者线程的请求。 所以iOS可能会很好。

我的iOS 10+的Swift 3.0解决schemetimerMethod()将在后台队列中调用。

 class ViewController: UIViewController { var timer: Timer! let queue = DispatchQueue(label: "Timer DispatchQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil) override func viewDidLoad() { super.viewDidLoad() queue.async { [unowned self] in let currentRunLoop = RunLoop.current let timeInterval = 1.0 self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.timerMethod), userInfo: nil, repeats: true) self.timer.tolerance = timeInterval * 0.1 currentRunLoop.add(self.timer, forMode: .commonModes) currentRunLoop.run() } } func timerMethod() { print("code") } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) queue.sync { timer.invalidate() } } } 

仅Swift (虽然可能可以修改用于Objective-C)

https://github.com/arkdan/ARKExtensions中检出;DispatchTimer ,它“以指定的时间间隔在指定的次数(可选)上执行指定次数的closures。

 let queue = DispatchQueue(label: "ArbitraryQueue") let timer = DispatchTimer(timeInterval: 1, queue: queue) { timer in // body to execute until cancelled by timer.cancel() }