如何在后台线程上创建 NSTimer?

发布于 2024-12-19 00:15:25 字数 154 浏览 6 评论 0 原文

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

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

I have a task that needs to be performed every 1 second. Currently I have an NSTimer firing repeatedly every 1 sec. How do I have the timer fire in a background thread (non UI-thread)?

I could have the NSTimer fire on the main thread then use NSBlockOperation to dispatch a background thread, but I'm wondering if there is a more efficient way of doing this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

月竹挽风 2024-12-26 00:15:25

如果您需要这样做,以便在滚动视图(或地图)时计时器仍然运行,则需要将它们安排在不同的运行循环模式上。替换您当前的计时器:

[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 仍然在主线程上运行,仍然在与滚动视图相同的运行循环上。区别在于运行循环模式。查看博客文章以获得清晰的解释。

If you need this so timers still run when you scroll your views (or maps), you need to schedule them on different run loop mode. Replace your current timer:

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

With this one:

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

For details, check this blog post: Event tracking stops NSTimer

EDIT :
second block of code, the NSTimer still runs on the main thread, still on the same run loop as the scrollviews. The difference is the run loop mode. Check the blog post for a clear explanation.

忘年祭陌 2024-12-26 00:15:25

如果您想使用纯 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 秒的回旋余地来触发这些事件,如果您愿意,您可以收紧该回旋余地。

If you want to go pure GCD and use a dispatch source, Apple has some sample code for this in their Concurrency Programming Guide:

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
}

You could then set up your one-second timer event using code like the following:

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
});

making sure to store and release your timer when done, of course. The above gives you a 1/10th second leeway on the firing of these events, which you could tighten up if you desired.

獨角戲 2024-12-26 00:15:25

计时器需要安装到在已经运行的后台线程上运行的运行循环中。该线程必须继续运行运行循环才能真正触发计时器。为了让后台线程继续能够触发其他计时器事件,它需要生成一个新线程来实际处理事件(当然,假设您正在执行的处理需要花费大量时间)。

不管它的价值如何,我认为通过使用 Grand Central Dispatch 或 NSBlockOperation 生成新线程来处理计时器事件是对主线程的完全合理的使用。

The timer would need to be installed into a run loop operating on an already-running background thread. That thread would have to continue to run the run loop to have the timer actually fire. And for that background thread to continue being able to fire other timer events, it would need to spawn a new thread to actually handle events anyway (assuming, of course, that the processing you're doing takes a significant amount of time).

For whatever it's worth, I think handling timer events by spawning a new thread using Grand Central Dispatch or NSBlockOperation is a perfectly reasonable use of your main thread.

饭团 2024-12-26 00:15:25

这应该可行,

它在后台队列中每 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];
});

希望有帮助

This should work,

It repeats a method every 1 second in a background queue without using 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];
    });
}

If you are in the main queue and you want to call above method you could do this so it changes to a background queue before is run :)

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

Hope it helps

咽泪装欢 2024-12-26 00:15:25

对于 swift 3.0,

Tikhonv 的回答并没有解释太多。这里补充一下我的一些理解。

首先,为了简单起见,这里是代码。它与我创建计时器的地方的 Tikhonv 代码不同。我使用构造函数创建计时器并将其添加到循环中。我认为scheduleTimer函数会将计时器添加到主线程的RunLoop中。所以最好使用构造函数来创建计时器。

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
    }
  }
}

创建队列

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

RunLoop 类通常不被认为是线程安全的,并且
它的方法只能在当前的上下文中调用
线。你永远不应该尝试调用 RunLoop 对象的方法
在不同的线程中运行,因为这样做可能会导致意外
结果。

所以我决定存储队列并为计时器使用相同的队列以避免同步问题。

还创建一个空计时器并存储在类变量中。将其设置为可选,以便您可以停止计时器并将其设置为零。

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

启动定时器

要启动定时器,首先从 DispatchQueue 调用 async。那么最好首先检查计时器是否已经启动。如果计时器变量不为 nil,则将其 invalidate() 并将其设置为 nil。

下一步是获取当前的 RunLoop。因为我们在创建的队列块中执行了此操作,所以它将获取我们之前创建的后台队列的 RunLoop。

创建计时器。这里我们没有使用scheduledTimer,而是调用timer的构造函数并传入你想要的定时器的任何属性,例如timeInterval、target、selector等。

将创建的定时器添加到RunLoop中。运行它。

这里有一个关于运行RunLoop的问题。根据此处的文档,它表示它实际上开始了一个无限循环,处理来自运行循环的输入源和计时器的数据。

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() {
  // under queue by default
  debug()
}

func debug() {
  let name = __dispatch_queue_get_label(nil)
  print(String(cString: name, encoding: .utf8))
}

上面的调试函数用于打印出队列的名称。如果你担心它是否一直在队列中运行,你可以调用它来检查。

停止计时器

停止计时器很简单,调用invalidate()并将存储在类中的计时器变量设置为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 有点困惑。根据此处的文档,似乎当没有附加计时器时,它将立即退出。所以当我们停止计时器时,它应该自行存在。然而,在该文件的最后,它还说:

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

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

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

我的想法是,在 iOS 上使用 .run() 可能是安全的。因为文档指出 macOS 会根据需要安装和删除额外的输入源,以处理针对接收者线程的请求。所以iOS可能没问题。

For swift 3.0,

Tikhonv's answer does not explain too much. Here adds some of my understanding.

To make things short first, here is the code. It is DIFFERENT from Tikhonv's code at the place where I create the timer. I create the timer using constructer and add it into the loop. I think the scheduleTimer function will add the timer on to the main thread's RunLoop. So it is better to create timer using the constructor.

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
    }
  }
}

Create Queue

First, create a queue to make timer run on background and store that queue as a class property in order to reuse it for stop timer. I am not sure if we need to use the same queue for start and stop, the reason I did this is because I saw a warning message here.

The RunLoop class is generally not considered to be thread-safe and
its methods should only be called within the context of the current
thread. You should never try to call the methods of an RunLoop object
running in a different thread, as doing so might cause unexpected
results.

So I decided to store the queue and use the same queue for the timer to avoid synchronization issues.

Also create an empty timer and stored in the class variable as well. Make it optional so you can stop the timer and set it to nil.

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

Start Timer

To start timer, first call async from DispatchQueue. Then it is a good practice to first check if the timer has already started. If the timer variable is not nil, then invalidate() it and set it to nil.

The next step is to get the current RunLoop. Because we did this in the block of queue we created, it will get the RunLoop for the background queue we created before.

Create the timer. Here instead of using scheduledTimer, we just call the constructor of timer and pass in whatever property you want for the timer such as timeInterval, target, selector, etc.

Add the created timer to the RunLoop. Run it.

Here is a question about running the RunLoop. According to the documentation here, it says it effectively begins an infinite loop that processes data from the run loop's input sources and timers.

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()
  }
}

Trigger Timer

Implement the function as normal. When that function gets called, it is called under the queue by default.

func timerTriggered() {
  // under queue by default
  debug()
}

func debug() {
  let name = __dispatch_queue_get_label(nil)
  print(String(cString: name, encoding: .utf8))
}

The debug function above is use to print out the name of the queue. If you ever worry if it has been running on the queue, you can call it to check.

Stop Timer

Stop timer is easy, call invalidate() and set the timer variable stored inside class to nil.

Here I am running it under the queue again. Because of the warning here I decided to run all the timer related code under the queue to avoid conflicts.

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

Questions related to RunLoop

I am somehow a little bit confused on if we need to manually stop the RunLoop or not. According to the documentation here, it seems that when no timers attached to it, then it will exits immediately. So when we stop the timer, it should exists itself. However, at the end of that document, it also said:

removing all known input sources and timers from the run loop is not a
guarantee that the run loop will exit. macOS can install and remove
additional input sources as needed to process requests targeted at the
receiver’s thread. Those sources could therefore prevent the run loop
from exiting.

I tried the solution below that provided in the documentation for a guarantee to terminate the loop. However, the timer does not fire after I change .run() to the code below.

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

What I am thinking is that it might be safe for just using .run() on iOS. Because the documentation states that macOS is install and remove additional input sources as needed to process requests targeted at the receiver's thread. So iOS might be fine.

愁以何悠 2024-12-26 00:15:25

六年后的今天,我尝试做同样的事情,这是替代解决方案:GCD 或 NSThread。

定时器与运行循环结合使用,线程的运行循环只能从线程中获取,因此关键是在线程中调度定时器。

除了主线程的runloop外,runloop需要手动启动;在运行的runloop中应该有一些事件需要处理,比如Timer,否则runloop将退出,如果timer是唯一的事件源,我们可以使用它来退出runloop:使定时器无效。

下面是Swift 4的代码:

解决方案0:GCD

weak var weakTimer: Timer?
@objc func timerMethod() {
    // vefiry whether timer is fired in background thread
    NSLog("It's called from main thread: \(Thread.isMainThread)")
}

func scheduleTimerInBackgroundThread(){
    DispatchQueue.global().async(execute: {
        //This method schedules timer to current runloop.
        self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
        //start runloop manually, otherwise timer won't fire
        //add timer before run, otherwise runloop find there's nothing to do and exit directly.
        RunLoop.current.run()
    })
}

Timer对target有强引用,runloop对timer有强引用,timer失效后释放target,所以在target中保留对它的弱引用,并在适当的时间失效退出runloop(然后退出线程)。

注意:作为一种优化,DispatchQueuesync 函数会尽可能调用当前线程上的块。实际上,您在主线程中执行上面的代码,定时器是在主线程中触发的,所以不要使用 sync 函数,否则定时器不会在您想要的线程中触发。

您可以通过暂停 Xcode 中执行的程序来命名线程来跟踪其活动。在GCD中,使用:

Thread.current.name = "ThreadWithTimer"

解决方案1:Thread

我们可以直接使用NSThread。不要害怕,代码很简单。

func configurateTimerInBackgroundThread(){
    // Don't worry, thread won't be recycled after this method return.
    // Of course, it must be started.
    let thread = Thread.init(target: self, selector: #selector(addTimer), object: nil)
    thread.start()
}

@objc func addTimer() {
    weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
    RunLoop.current.run()
}

解决方案2:子类Thread

如果你想使用Thread子类:

class TimerThread: Thread {
    var timer: Timer
    init(timer: Timer) {
        self.timer = timer
        super.init()
    }

    override func main() {
        RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
        RunLoop.current.run()
    }
}

注意:不要在init中添加计时器,否则,计时器将添加到init的调用者线程的runloop中,而不是该线程的runloop中,例如,您在主线程中运行以下代码,如果TimerThread 在init方法中添加定时器,定时器将被调度到主线程的runloop,而不是timerThread的runloop。您可以在 timerMethod() 日志中验证它。

let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
weakTimer = timer
let timerThread = TimerThread.init(timer: timer)
timerThread.start()

PS关于Runloop.current.run(),其文档建议如果我们希望runloop终止,不要调用此方法,使用run(mode: RunLoopMode, before limitDate: Date) code>,实际上是在NSDefaultRunloopMode中反复调用run()这个方法,什么模式呢?更多详细信息请参见 运行循环和线程

Today after 6 years, I try to do same thing, here is alternative soltion: GCD or NSThread.

Timers work in conjunction with run loops, a thread's runloop can be get from the thread only, so the key is that schedule timer in the thread.

Except main thread's runloop, runloop should start manually; there should be some events to handle in running runloop, like Timer, otherwise runloop will exit, and we can use this to exit a runloop if timer is the only event source: invalidate the timer.

The following code is Swift 4:

Solution 0: GCD

weak var weakTimer: Timer?
@objc func timerMethod() {
    // vefiry whether timer is fired in background thread
    NSLog("It's called from main thread: \(Thread.isMainThread)")
}

func scheduleTimerInBackgroundThread(){
    DispatchQueue.global().async(execute: {
        //This method schedules timer to current runloop.
        self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
        //start runloop manually, otherwise timer won't fire
        //add timer before run, otherwise runloop find there's nothing to do and exit directly.
        RunLoop.current.run()
    })
}

Timer has strong reference to target, and runloop has strong reference to timer, after timer invalidate, it release target, so keep weak reference to it in target and invalidate it in appropriate time to exit runloop(and then exit thread).

Note: as an optimization, syncfunction of DispatchQueue invokes the block on the current thread when possible. Actually, you execute above code in main thread, Timer is fired in main thread, so don't use sync function, otherwise timer is not fired at the thread you want.

You could name thread to track its activity by pausing program executing in Xcode. In GCD, use:

Thread.current.name = "ThreadWithTimer"

Solution 1: Thread

We could use NSThread directly. Don't afraid, code is easy.

func configurateTimerInBackgroundThread(){
    // Don't worry, thread won't be recycled after this method return.
    // Of course, it must be started.
    let thread = Thread.init(target: self, selector: #selector(addTimer), object: nil)
    thread.start()
}

@objc func addTimer() {
    weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
    RunLoop.current.run()
}

Solution 2: Subclass Thread

If you want to use Thread subclass:

class TimerThread: Thread {
    var timer: Timer
    init(timer: Timer) {
        self.timer = timer
        super.init()
    }

    override func main() {
        RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
        RunLoop.current.run()
    }
}

Note: don't add timer in init, otherwise, timer is add to init's caller's thread's runloop, not this thread's runloop, e.g., you run following code in main thread, if TimerThread add timer in init method, timer will be scheduled to main thread's runloop, not timerThread's runloop. You can verify it in timerMethod() log.

let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
weakTimer = timer
let timerThread = TimerThread.init(timer: timer)
timerThread.start()

P.S About Runloop.current.run(), its document suggest don't call this method if we want runloop to terminate, use run(mode: RunLoopMode, before limitDate: Date), actually run() repeatedly invoke this method in the NSDefaultRunloopMode, what's mode? More details in runloop and thread.

岁月染过的梦 2024-12-26 00:15:25

我适用于 iOS 10+ 的 Swift 3.0 解决方案,timerMethod() 将在后台队列中调用。

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()
        }
    }
}

My Swift 3.0 solution for iOS 10+, timerMethod() will be called in background queue.

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()
        }
    }
}
凉城已无爱 2024-12-26 00:15:25

仅限 Swift(尽管可以修改为与 Objective-C 一起使用)

DispatchTimer rel="nofollow noreferrer">https://github.com/arkdan/ARKExtensions,“以指定的时间间隔,在指定的调度队列上执行指定次数的闭包(可选)。”

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

Swift only (although can probably be modified to use with Objective-C)

Check out DispatchTimer from https://github.com/arkdan/ARKExtensions, which "Executes a closure on specified dispatch queue, with specified time intervals, for specified number of times (optionally). "

let queue = DispatchQueue(label: "ArbitraryQueue")
let timer = DispatchTimer(timeInterval: 1, queue: queue) { timer in
    // body to execute until cancelled by timer.cancel()
}
夏见 2024-12-26 00:15:25
class BgLoop:Operation{
    func main(){
        while (!isCancelled) {
            sample();
            Thread.sleep(forTimeInterval: 1);
        }
    }
}
class BgLoop:Operation{
    func main(){
        while (!isCancelled) {
            sample();
            Thread.sleep(forTimeInterval: 1);
        }
    }
}
木落 2024-12-26 00:15:25

如果你想让你的 NSTimer 在均匀的后台运行,请执行以下操作 -

  1. 在 applicationWillResignActive 方法中
  2. 调用 [self beginBackgroundTask] 方法 在 applicationWillEnterForeground 中调用 [self endBackgroundTask] 方法

就是这样

-(void)beginBackgroundTask
{
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundTask];
    }];
}

-(void)endBackgroundTask
{
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}

If you want your NSTimer to run in even background, do the following-

  1. call [self beginBackgroundTask] method in applicationWillResignActive methods
  2. call [self endBackgroundTask] method in applicationWillEnterForeground

That's it

-(void)beginBackgroundTask
{
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundTask];
    }];
}

-(void)endBackgroundTask
{
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文