如何将计时器移到后台线程

use*_*748 6 nstimer grand-central-dispatch ios swift

我的应用程序中目前有两个NSTimer计时器,它们负责获取数据和更新UI。最近我注意到,当计时器运行时,我的UI性能很差,例如,用户无法很好地滚动UITableview或根本无法滚动。我在其他地方读过,可以将计时器推入不同的运行循环中,这可能会有所帮助。这是我的计时器现在的样子:

let aSelector : Selector = "updateLocation"
timer = NSTimer.scheduledTimerWithTimeInterval(((NSUserDefaults.standardUserDefaults().stringForKey("mapUpdate")! as NSString).doubleValue), target: self, selector: aSelector, userInfo: nil, repeats: true)
Run Code Online (Sandbox Code Playgroud)

如何修改Swift计时器,使其在后台线程中运行?我一直在阅读,但一直感到困惑。

Ami*_*eza 12

如果计时器在主线程的运行循环上触发,则UI或计时器可能会出现问题。

例如,假设您触发了一个计时器。您的应用程序中还有一个tableView,它向用户显示一个列表,并且用户正在滚动表。同时,计时器的时间过去了。我们希望计时器的代码块可以立即执行,但是直到用户结束滚动操作才执行。在许多情况下,这不是我们想要的。

实际上,此问题以及您在问题中提到的问题的根源在于,您已经在主线程上运行了计时器,在该线程上,您的任务和所有UI操作都是按顺序处理的。串行表示仅在上一个任务完成时才执行一个任务。

为了解决这个问题,一种解决方案是在后台线程的运行循环上调用计时器,如下所示:

DispatchQueue.global(qos: .background).async {
    let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: selector(fireTimer), repeats: false)
    let runLoop = RunLoop.current
    runLoop.add(timer, forMode: .defaultRunLoopMode)
    runLoop.run()
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您的计时器被重复调用,请不要忘记在任务结束时调用计时器的invalidate(),否则运行循环将对计时器的目标对象保持强烈的引用,这可能导致内存泄漏。

还有一个更好的选项来代替Timer类。您可以使用DispatchSourceTimer。

有关更多信息,请参考以下链接:


Dav*_*rry 2

要在不同的运行循环上安排计时器,您可以使用:

// Create an unscheduled timer
let timer = NSTimer(
    ((NSUserDefaults.standardUserDefaults().stringForKey("mapUpdate")! as NSString).doubleValue),
    target: self,
    selector: "updateLocation",
    userInfo: nil,
    repeats: true)

// Add the timer to a runloop (in this case the main run loop)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
Run Code Online (Sandbox Code Playgroud)

请注意,我不确定这是否真正解决了您的问题,甚至不确定这是否是处理问题的好方法。从您的进一步讨论中,我想我可能会关注一些使用的东西CLLocationManager.startMonitoringSignificantLocationChanges,这将消除定期检查位置的需要,只需在它发生显着变化时进行更新,其中显着被适当定义。请注意,速度和航向可直接从 获取,CLLocation因此以精确的时间间隔更新并不重要。