Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/shell/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift 终止并重新运行后台计时器(DispatchSourceTimer)_Swift_Grand Central Dispatch_Nstimer_Dispatch Async - Fatal编程技术网

Swift 终止并重新运行后台计时器(DispatchSourceTimer)

Swift 终止并重新运行后台计时器(DispatchSourceTimer),swift,grand-central-dispatch,nstimer,dispatch-async,Swift,Grand Central Dispatch,Nstimer,Dispatch Async,在游戏屏幕上,我使用一个后台队列来计算游戏中经过的时间,这个函数基于一个完美的解决方案:它允许用户在计时器仍然打开的情况下浏览其他VC。 VC层次结构非常简单:游戏设置VC在Tabbar控制器中处理。游戏屏幕是分开的。用户可以在计时器打开时更改设置。设置存储在CoreData中 在我需要显示计时器的游戏屏幕中,我有一个标签,显示经过的时间和两个按钮:播放/暂停按钮和重置按钮 我在ViewDidLoad中调用我的设置计时器func。 my timer的默认值是CoreData中存储的值,它已在设置

在游戏屏幕上,我使用一个后台队列来计算游戏中经过的时间,这个函数基于一个完美的解决方案:它允许用户在计时器仍然打开的情况下浏览其他VC。 VC层次结构非常简单:游戏设置VC在Tabbar控制器中处理。游戏屏幕是分开的。用户可以在计时器打开时更改设置。设置存储在CoreData中

在我需要显示计时器的游戏屏幕中,我有一个标签,显示经过的时间和两个按钮:播放/暂停按钮和重置按钮

我在ViewDidLoad中调用我的设置计时器func。 my timer的默认值是CoreData中存储的值,它已在设置中定义。当定时器打开时,该值每秒递增1。 我还有一个静态let共享,它保持计时器状态(恢复/挂起)

当我在游戏屏幕上时,如果我的计时器被暂停,我的“播放/暂停”按钮工作正常:我可以导航到其他视图(意味着退出我的屏幕游戏),再次显示我的屏幕游戏并恢复计数器。它正确地更新了我的标签

问题是当我在计时器运行时关闭游戏屏幕视图时。计时器工作(print func显示计时器仍在运行),但当我再次显示屏幕时,我无法暂停/恢复/重新启动它,并且我的标签在我回来的那一刻就停止了。。。当计时器仍在运行时

private var counter: Int16?
var t = RepeatingTimer(timeInterval: 1)
let gameIsOn = isGameOnManager.shared

override func viewDidLoad() {
        super.viewDidLoad()
        print("is timer On ? \(String(describing: gameIsOn.isgameOn))")
        buildTimer()
        if gameIsOn.isgameOn == true {
            resumeTapped = true
            t.resume()
            PlayB.setImage(UIImage(named:"pause"), for: .normal)
        } else {
            resumeTapped = false
        }
    }

    func buildTimer(){
        self.t.eventHandler = {
            self.counter! += 1
            print("counter \(String(describing: self.counter!))")
            self.coreDataEntity?.TimeAttribute = self.counter ?? 0
            self.save()
            DispatchQueue.main.async {
                self.dataField.text = String(describing: self.counter ?? 0)
            }
        }
    }

@objc func didTapButton(_ button: UIButton) {
        if resumeTapped == false {
            t.resume()
            resumeTapped = true
            gameIsOn.isgameOn = true
            PlayB.setImage(UIImage(named:"pause"), for: .normal)
        }
        else if resumeTapped == true {
            t.suspend()
            resumeTapped = false
            gameIsOn.isgameOn = false
            PlayB.setImage(UIImage(named:"play"), for: .normal)
        }
    }

在视图控制器、计时器和计时器的事件处理程序之间有一个强引用循环。您应该在闭包中使用
引用来打破此循环:

func buildTimer() {
    t.eventHandler = { [weak self] in
        guard let self = self else { return }

        ...
    }
}
这修正了强引用循环


但当您修复此问题时,当您关闭此视图控制器时,可能会看到计时器停止运行。如果是这种情况,则表明计时器不在正确的对象中。它应该存在于整个应用程序中的某个更高级别的对象中,而不是在显示和取消的视图控制器中。

什么是“离开”和“回来”?在许多情况下,视图控制器在离开时会被拆下。当你离开时,你需要将当前时间保存到永久性存储中,这样当你返回时,你就可以从现在开始自动启动计时器。@matt,当“我回来”(我指的是改变视图并回到我的VC)时,它已经将计数器值存储在CoreData中,因此该值是可以的,但不再实现,它不会重新启动。。。我没有写完整的函数,但是我的save函数还可以。如果你需要帮助,请提供足够的代码来重现这个问题。你有很多代码,只是在你的问题中提到的。我们无法想象或猜测它的作用。@matt,我编辑了存储计数器值的函数。@Rob,我重新编辑了我的问题,似乎我的问题更多的是关于我停止(杀死)并重新启动后台计时器的方式,而不是视图层次结构的问题,但我同意,这并不清楚……你让我开心!DispatchSourceTimer确实具有强引用。因此,您的解决方案完美地保持了我的按钮连接和标签更新。当我关闭游戏屏幕时,计时器功能齐全。。。令人惊讶的是,当我重新呈现VC:counter标签时,我的打印功能中有两个计数器。。。它重新创建了一个新的计时器。。。因此,为了确保只保留1个dispatchSourceTimer,我将我的计时器分配为共享let,并且每次呈现它时,我清空timer.eventHandler,取消计时器并重建一个。这有点棘手,但它是有效的。。。