在Swift中使用NSTimer

48
在这种情况下,timerFunc()从未被调用。我错过了什么?
class AppDelegate: NSObject, NSApplicationDelegate {

    var myTimer: NSTimer? = nil

    func timerFunc() {
        println("timerFunc()")
    }

    func applicationDidFinishLaunching(aNotification: NSNotification?) {
        myTimer = NSTimer(timeInterval: 5.0, target: self, selector:"timerFunc", userInfo: nil, repeats: true)
    }
}

如果您使用计时器的 init 方法,必须使用 addTimer:forMode: 将新计时器添加到运行循环中。这是文档说明中的第二个句子。否则,使用 scheduledTimerWithTimeInterval 方法,这可能是您正在寻找的内容。 - Firo
为什么会有“踩”的情况呢,考虑到有很多人都提供了不同的回答? - RobertJoseph
1
我不能确定,但很可能是因为答案并不难找,就像我之前指出的那样。它就在文档中。如果你在方法上使用了option点击,你会在5秒内找到解决方案,而且甚至不用离开Xcode。 - Firo
10个回答

98

您可以创建一个定时器,该定时器会自动添加到运行循环并开始触发:

Swift 2

NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "timerDidFire:", userInfo: userInfo, repeats: true)

Swift 3、4、5

Timer.scheduledTimer(withTimeInterval: 0.5, target: self, selector: #selector(timerDidFire(_:)), userInfo: userInfo, repeats: true)

或者,你可以保留你当前的代码,在准备好时将计时器添加到runloop中:

Swift 2

let myTimer = NSTimer(timeInterval: 0.5, target: self, selector: "timerDidFire:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(myTimer, forMode: NSRunLoopCommonModes)

Swift 3、4、5

let myTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(timerDidFire(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(myTimer, forMode: RunLoop.Mode.common)

如何暂停和恢复此计时器? - Qadir Hussain
myTimer.invalidate()会取消计时器。除此之外,NSTimer不支持任何暂停/恢复功能。你可以想象一个简单的子类,通过(1)记录开始时间,(2)记录暂停时间,和(3)在恢复时,运行比我们原来的timeInterval短t2-t1的时间来添加这个支持。 - Ryan
1
Sift 3.0的运行循环语法:RunLoop.current.add(myTimer, forMode: RunLoopMode.commonModes) - Florin Odagiu
3
Swift3 RunLoop.current.add(timer, forMode: RunLoopMode.commonModes) 可以翻译为:将计时器添加到当前运行循环的公共模式中。 - nikans

9
我会用类似Luke的方法进行翻译。但是对于那些坚持“私有方法”的人来说,需要注意一点:在Swift中不要将回调函数设为私有。
如果您写成这样:
private func timerCallBack(timer: NSTimer){

你会得到:

timerCallBack:]: 未识别的选择器发送到实例...由于未捕获的异常'NSInvalidArgumentException'而终止应用程序


非常感谢您! - swift taylor
4
只需在私有方法前面加上“@objc”,它也会起作用: @objc private func timerCallBack(timer: NSTimer){ - Lensflare
@Lensflare的建议答案是正确的,你只需要使用@objc方法修饰符。 - kyleturner
是的,我知道"@objc",但我们正在谈论"Swift"纯粹主义者...所以避免所有来自objc的旧交易。(我也很喜欢它..) - ingconti

7

除非使用 NSTimer.scheduledTimerWithTimeInterval,否则NSTimer不会自动调度:

myTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "timerFunc", userInfo: nil, repeats: true)

6

Swift 3 版本:

var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true);
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)

2
使用 scheduledTimer() 方法将自动将您的计时器添加到运行循环中 - 您无需手动添加。 - Craig Otis

6

正如Drewag和Ryan所指出的,您需要创建一个定时器(或自己安排计划)。最简单的方法是使用以下已经预定好的方式创建:

myTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "timerFunc:", userInfo: nil, repeats: true)

你还需要修改timerFunc(和相关选择器)的定义,以接受一个参数,并以“:”结尾。
func timerFunc(timer:NSTimer!) {
    ...
}

2
你不需要定义它来接受一个参数。两种方式都可以。 - drewag
1
@drewag 好的,我刚想起来在SO上有一些关于没有':'无法正常工作的讨论,所以我在playground中尝试了一下。如果您(正确地)定义回调函数需要一个参数,则需要“:”。 “正确”是因为NSTimer文档指出回调必须接受单个参数。“选择器应具有以下签名:timerFireMethod:(包括冒号以表示该方法需要一个参数)。似乎您可以不带有:或参数,但基于文档,我仍然认为我的答案是正确的 :) - David Berry
1
在 IBActions 和我曾经定义选择器的任何其他地方,参数也是可选的(至少有一个参数)。这只是运行时的本质。他省略了参数并不影响为什么它没有被调用。最坏的情况是会抛出一个关于找不到选择器的异常,而不是静默失败。 - drewag

5

运行循环的Swift 3.0语法:

RunLoop.current.add(myTimer, forMode: .commonModes)

4
这是一段代码,演示了如何使用和不使用参数调用一个延迟函数。
在xCode中的新项目(singleViewApplication)中使用此代码,并将其放入标准viewController中。
class ViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("delayedFunctionWithoutParameter:"), userInfo: nil, repeats: false)

        let myParameter = "ParameterStringOrAnyOtherObject"

        NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: Selector("delayedFunctionWithParameter:"), userInfo: myParameter, repeats: false)
    }

    // SIMPLE TIMER - Delayed Function Call
    func delayedFunctionWithoutParameter(timer : NSTimer) {
        print("This is a simple function beeing called without a parameter passed")
        timer.invalidate()
    }

    // ADVANCED TIMER - Delayed Function Call with a Parameter
    func delayedFunctionWithParameter(timer : NSTimer) {

        // check, wether a valid Object did come over
        if let myUserInfo: AnyObject = timer.userInfo {
            // alternatively, assuming it is a String for sure coming over
            // if let myUserInfo: String = timer.userInfo as? String {
            // assuming it is a string comming over
            print("This is an advanced function beeing called with a parameter (in this case: \(myUserInfo)) passed")
        }

        timer.invalidate()
    }
}

请注意,无论如何,您都应该使用参数 (timer:NSTimer) 实现延迟函数,以便能够使计时器失效(终止、结束)。并且通过传递的 "timer",您也可以访问 userInfo(在那里,您可以放置任何对象,不仅限于字符串对象,还包括集合类型,例如数组和字典)。
原始的Apple文档说:“计时器将自身作为参数传递,因此方法会采用以下模式:- (void) timerFireMethod:(NSTimer *) timer”。详情请阅读这里

3

由于这个线程让我尝试自己在RunLoop上设置计时器(解决了我的问题),我也把我的具体情况发布出来,谁知道可能有所帮助。我的计时器是在应用程序启动和所有对象初始化期间创建的。我的问题是,虽然它确实安排了计时器,但它仍然从未触发。我猜测,这是因为 scheduledTimerWithTimeInterval 在App启动过程中将计时器放在了不同的RunLoop上。如果我只初始化计时器,然后使用NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)代替,则可以正常工作。


3

使用Swift3,您可以使用以下方式运行:

var timer: Timer?
func startTimer() {

    if timer == nil {
        timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.loop), userInfo: nil, repeats: true)
    }
}

func stopTimer() {
    if timer != nil {
        timer?.invalidate()
        timer = nil
    }
}

func loop() {
    //do something
}

2
要按照提问者建议的方法实现,您需要将其添加到运行循环中:
myTimer = NSTimer(timeInterval: 5.0, target: self, selector:"timerFunc", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)

文档还说目标应该带有一个参数,但是没有也可以运行。
func timerFireMethod(timer: NSTimer) { }

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