ios - 在 Swift 上设置定时器

标签 ios swift swift3 timer

我尝试多次执行函数 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()

最佳答案

我建议:

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

    var timer = Timer()
    

    那是创建一个空白的计时器实例。我们不想那样做。您应该改用:

    weak var timer: Timer?
    

    这实现了一些事情:

    • Timer? 语法表明它是一个“可选的”,您稍后将实例化它的值。

    • Timer 被调度时,runloop 会保持对它的强引用。因此,与大多数其他对象不同,您个人不需要保留对计划计时器的强引用。当计时器失效时,您可能希望 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()
}

不过,应该认识到,您最终可能会得到类似于强引用循环的东西:

  • runloop 保持对预定定时器的强引用;
  • 基于选择器的计时器对其目标保持强引用;
  • MyClass(target)大概负责最终使该计时器无效。

因此,在 Timer 失效之前,MyClass 无法被释放。而且,就目前而言,您不能只是在 MyClassdeinitinvalidate Timer,因为 deinit 在计时器失效之前不会被调用。

最终效果是,如果你有,例如,这个 MyClass 作为你的 View Controller 的属性,启动计时器然后关闭 View Controller ,计时器将继续运行并且 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()
    }
}

关于ios - 在 Swift 上设置定时器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48686960/

相关文章:

ios - 将以编程方式调用的 segue 的发件人传递到目的地

ios - 如何为 cordova 3.5 联系人插件设置 config.xml

ios - 每次用户单击时快速更改按钮的图像

ios - 使用 MapKit 生成特定距离的路线

ios - 我希望能够按照标签指定的顺序编辑文本字段。研究了很多都无济于事。我有点菜鸟

ios - 在主视图 Controller 中单击后更新外部 View Controller 中的 UITextField

ios - View 中的图像大于设备屏幕

ios - 停止从 webview 进行深层链接导航

iOS Swift 3 将 NSArray 值存储到 Realm

ios - 手势抛出无法识别的选择器错误