Swift Timer.scheduledTimer()不起作用 [英] Swift Timer.scheduledTimer() doesn't work

查看:386
本文介绍了Swift Timer.scheduledTimer()不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的快速应用程序中有两个视图.我正在执行以下segue.

I have two views in my swift app. I am performing a segue as below.

ViewController.swift -----------------> GameViewController.swift

ViewController.swift -----------------> GameViewController.swift

在加载GameViewController时,还会将一个值数组从ViewController.swift传递给GameViewController.swift

When loading the GameViewController an value array also passed to GameViewController.swift from ViewController.swift

应该在GameViewController.swift中初始化计时器

A timer should be initialized in GameViewController.swift

我试图初始化一个计时器并通过它调用一个方法,但是它不起作用.

I tried to initialize a timer and call a method through it, but it doesn't work.

以下是我的代码段.

ViewController.swift

ViewController.swift

func signIn(difficultyLvl:String){
    let username = usernameTxt.text
    let password = passwordTxt.text

    let url = URL(string: "http://192.168.1.106/speed/scoreBoardController.php?username="+username!+"&password="+password!+"&action=SIGNIN")

    let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
        let isPassed = String(data: data!, encoding:.utf8)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

        var gameViewControllerParams = [Int: [String: String]]()
        gameViewControllerParams[0] = ["userId" : isPassed!]
        gameViewControllerParams[1] = ["difficultyLvl" : difficultyLvl]

        if(isPassed != "null"){
            self.performSegue(withIdentifier: "gotoGame", sender: gameViewControllerParams)
        }
    }

    task.resume()
}

GameViewController.swift

GameViewController.swift

class GameViewController: UIViewController {

    var gameViewControllerParams = [Int: [String: String]]()

    override func viewDidLoad() {
        super.viewDidLoad()

        let _ = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(self.setCalculationLs), userInfo:nil,repeats: true)
    }

    func setCalculationLs(){
        print("Timing")
    }

}

推荐答案

计时器在后台队列上不起作用(不费吹灰之力就涉及创建运行循环或在现有运行循环上手动进行调度).但是无论如何,您永远都不应从主队列之外的任何其他位置启动任何UI更新.

Timers don't work on background queues (without some sleight of hand involving creating run loops or manually scheduling it on an existing run loop). But you should never initiate any UI update from anything other than the main queue, anyway.

因此,由于您要从URLSession完成闭包(在后台队列中运行)调用performSegue,因此它实际上也在后台队列中运行viewDidLoad.因此,计划计时器的尝试失败.要解决此问题,您必须手动将performSegue代码分派到主队列:

So, since you're calling performSegue from a URLSession completion closure (which runs on a background queue), it's actually running viewDidLoad from the background queue, too. Thus the attempt to schedule the timer is failing. To get around this, you have to manually dispatch the performSegue code to the main queue:

let task = URLSession.shared.dataTask(with: url!) { data, response, error in
    ...

    if isPassed != "null" {
        DispatchQueue.main.async {
            self.performSegue(withIdentifier: "gotoGame", sender: ...)
        }
    }
}

如果您不确定是否在主队列上运行了某些代码,请参考文档.或者,您可以使用调度先决条件:

If you're ever unsure whether some code is running on the main queue or not, refer to the documentation. Or you can use a dispatch precondition:

dispatchPrecondition(condition: .onQueue(.main))

这样,如果您不小心从后台队列中调用了代码,它将(在调试版本中)停止应用程序.

That way it will (in debug builds) stop the app if you've accidentally invoked the code from a background queue.

与当前问题无关,但顺便说一句,为避免计时器和视图控制器之间的引用周期过长,通常希望保留对计时器的引用,以便在视图消失时可以invalidate (例如,在viewDidAppear中创建计时器,然后在viewDidDisappear中将其删除).否则,您可以在GameViewController被关闭后保留它,例如:

Unrelated to your current problem, but as an aside, to avoid a strong reference cycle between the timer and the view controller, you generally want to keep a reference to the timer so that you can invalidate it when the view disappears (e.g. create timer in viewDidAppear and remove it in viewDidDisappear). Otherwise you can end up retaining the GameViewController after it was dismissed, e.g.:

class GameViewController: UIViewController {

    weak var timer: Timer?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        timer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(setCalculationLs(_:)), userInfo: nil, repeats: true)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        timer?.invalidate()
    }

    @objc func setCalculationLs(_ timer: Timer) {
        print("Tick")
    }
}

或者在iOS 10或更高版本中,您可以将基于块的变体与对selfweak引用和deinit中的invalidate引用一起使用:

Or in iOS 10 or later, you can use the block-based variant with weak reference to self, and invalidate in deinit:

class GameViewController: UIViewController {

    weak var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()

        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
            self?.setCalculationLs()
        }
    }

    deinit {
        timer?.invalidate()
    }

    func setCalculationLs() {
        print("Tick")
    }

}

这篇关于Swift Timer.scheduledTimer()不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆