在Swift中设置计时器

8

我正在尝试多次执行函数pepe(),没有出现错误,但它没有起作用。

这是我的代码:

public class MyClass {
    var timer = Timer()
    @objc func pepe() -> String {
        let hola = "hola"
        return hola
    }
    func startTimer(){
         let seconds = 1.0
        timer = Timer.scheduledTimer(timeInterval: seconds, target: ().self, selector: #selector(pepe), userInfo: nil, repeats: false)

    }
    func stopTimer() {

        timer.invalidate()

    }

    init() {
        self.startTimer()
        self.stopTimer()
    }
}
var pepe = MyClass()
pepe.stopTimer()
pepe.startTimer()
3个回答

15
我建议:
  1. 不要实例化一个空的 Timer。考虑:

    var timer = Timer()
    

    这将创建一个空的计时器实例,但我们不想这样做。相反,您应该使用:

    weak var timer: Timer?
    

    这样做有几个好处:

    • Timer? 语法表示它是一个“可选项”,其值稍后将被实例化。

    • 当计时器被调度时,运行循环会持有它的强引用。因此,与大多数其他对象不同,您不需要自己保持计时器的强引用。并且当计时器失效时,您可能希望 timer 变量自动设置为 nil。因此,weak 修饰符表示当计时器失效时,timer 变量将自动设置为 nil

  2. pepe 方法签名不太正确:

    • 它不应该返回任何东西;

    • 你应该给它一个 Timer 参数。这是一个好习惯。你可能在这里不需要它,但它使方法的意图更加清晰,而且你最终可能会发现拥有那个 Timer 参数很有用;以及

    • 我可能会给它一个更具描述性的名称,以避免任何歧义。我倾向于使用像 timerHandler 这样的名称。

  3. init 中启动和停止计时器没有意义。

  4. target 中引用 ().self 应该改为 self

  5. 在您的 playground 中,您正在停止尚未启动的计时器然后再启动它。

    您可能还希望在一段时间后停止它,这样您就有机会看到 Timer 的效果。

  6. 但是,作为一般规则,在编写启动计时器的方法时,最好确保您没有(意外地)已经启动了它。如果您不这样做,并意外调用了两次 startTimer,您可能会同时启动多个计时器(更糟糕的是,丢失对早期计时器的引用)。一个常见的解决方案是在创建下一个计时器之前查看是否已经有计时器存在,如果有,则使其失效。这可以使用可选链模式轻松实现:

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any
    
        // now proceed with scheduling of new timer
    }
    

因此:

import UIKit

// if doing this in playground, include the following two lines

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

// MyClass

public class MyClass {
    weak var timer: Timer?

    @objc func timerHandler(_ timer: Timer) {
        let hola = "hola"
        print(">>>> \(hola)")
    }

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any

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

    func stopTimer() {
        timer?.invalidate()
    }
}

var object = MyClass()
object.startTimer()

// stop it 10 seconds later
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
    object.stopTimer()
}

然而,需要认识到,你可能会得到类似于强引用循环的东西:

  • 运行循环对预定计时器保持强引用;
  • 基于选择器的计时器对其目标保持强引用;
  • MyClass(即target)可能负责最终使该计时器无效。

因此,只有在Timer被使无效之前,MyClass才能被解除分配。并且,按照现状,在MyClassdeinit中无法仅仅invalidate Timer,因为直到定时器失效之前deinit都不会被调用。

结果就是,如果你将这个MyClass作为您视图控制器的属性,并启动计时器,然后关闭视图控制器,计时器将继续运转,MyClass将不会被解除分配。

要解决这个问题,可以使用带有[weak self]引用的闭包计时器,消除计时器和MyClass之间的强引用关系。当MyClass被取消分配时,您还可以自动使计时器失效:

public class MyClass {
    weak var timer: Timer?

    deinit {
        timer?.invalidate()
    }

    func timerHandler(_ timer: Timer) {
        let hola = "hola"
        print(">>>> \(hola)")
    }

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any

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

    func stopTimer() {
        timer?.invalidate()
    }
}

谢谢你详细的回答,兄弟。这对我很有帮助。 - Umair_UAS

3

设置 repeats = true ,使其为 self 而不是 () .self,我尝试了这个方法并且它可以工作。另外你在init中开启和停止计时器的做法是错误的。

public class MyClass {
    var timer = Timer()
    @objc func pepe()  {
        let hola = "hola"
        print("\(hola)")
    }
    func startTimer(){
        let seconds = 1.0
        timer = Timer.scheduledTimer(timeInterval: seconds, target:self, selector: #selector(pepe), userInfo: nil, repeats: true)

    }
    func stopTimer() {

        timer.invalidate()

    }

    init() {

    }
}

你在运行 playground 吗?我遇到了这个错误: 错误:执行被中断,原因:EXC_BAD_ACCESS(代码=1,地址=0x0) - Juan Burolleau

0

试一下这个

 import UIKit
import AVFoundation

class PeopleViewController: UIViewController {

    var countdownTimer: Timer!
    var totalTime = 0

    let timerLbl: UILabel = {

        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.font = .boldSystemFont(ofSize: 22)
        lbl.text = "HH:MM:SS"
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        totalTime = defaults.integer(forKey: "time")
        view.backgroundColor = .white
        view.addSubview(timerLbl)

        NSLayoutConstraint.activate([

            timerLbl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            timerLbl.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            timerLbl.heightAnchor.constraint(equalToConstant: 30)
            ])
     //   startTimer()

//        doBackgroundTask()

        UIApplication.shared.runInBackground({
            self.startTimer()
        }) {
            // task after expiration.
        }
//        DispatchQueue.main.async(execute: {
//
//            NotificationCenter.default.addObserver(self, selector:#selector(self.startTimer), name:UIApplication.willEnterForegroundNotification, object: nil)
//
//        })
    }



//    deinit {
//        NotificationCenter.default.removeObserver(self)
//    }

//    var backgroundUpdateTask: UIBackgroundTaskIdentifier?
//
//    func doBackgroundTask() {
//
//        DispatchQueue.main.async {
//
//            self.beginBackgroundUpdateTask()
//
//            self.StartupdateLocation()
//
//            self.endBackgroundUpdateTask()
//
//        }
//    }

//    func beginBackgroundUpdateTask() {
//        self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
//            self.endBackgroundUpdateTask()
//        })
//    }
//
//    func endBackgroundUpdateTask() {
//        if let bgTask = self.backgroundUpdateTask {
//            UIApplication.shared.endBackgroundTask(bgTask)
//            self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
//        }
//    }
//
//    func StartupdateLocation() {
//        startTimer()
//    }

    let defaults = UserDefaults.standard
    @objc func startTimer() {
        print(countdownTimer)
        countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
    }

    @objc func updateTime() {

        print(timerLbl.text as Any)
        defaults.set(totalTime, forKey: "time")
        let updateTime = defaults.integer(forKey: "time")
        timerLbl.text = "\(timeFormatted(updateTime))"

        totalTime += 1

        defaults.set(totalTime, forKey: "time")
//        if totalTime != 0 {
//            totalTime += 1
//        } else {
//            endTimer()
//        }


    }

    func endTimer() {
        countdownTimer.invalidate()
    }

    func timeFormatted(_ totalSeconds: Int) -> String {
        let seconds: Int = totalSeconds % 60
        let minutes: Int = (totalSeconds / 60) % 60
        let hours: Int = totalSeconds / 3600
        return String(format: "%02d:%02d:%02d", hours,minutes, seconds)
    }


}


extension UIApplication {
    /// Run a block in background after app resigns activity
    public func runInBackground(_ closure: @escaping () -> Void, expirationHandler: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            let taskID: UIBackgroundTaskIdentifier
            if let expirationHandler = expirationHandler {
                taskID = self.beginBackgroundTask(expirationHandler: expirationHandler)
            } else {
                taskID = self.beginBackgroundTask(expirationHandler: { })
            }
            closure()
            self.endBackgroundTask(taskID)
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接