Использование NSTimer в Swift

#swift #nstimer

#swift #nstimer

Вопрос:

В этом сценарии 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)
    }
}
  

Комментарии:

1. Если вы используете таймер init , вы должны You must add the new timer to a run loop, using addTimer:forMode: . Это второе предложение в описании документа. В противном случае используйте scheduledTimerWithTimeInterval , вероятно, то, что вы искали.

2. Почему голоса против, учитывая, что несколько человек дали разные ответы?

3. Не могу сказать вам наверняка, но, вероятно, это потому, что ответ нетрудно найти самостоятельно, как я указывал ранее. Это прямо в документах. Если бы вы option нажали на свой метод, вы бы нашли решение в течение 5 секунд и даже не выходили из Xcode.

Ответ №1:

Вы можете создать таймер по расписанию, который автоматически добавляется в цикл выполнения и начинает запускаться:

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)
  

Или вы можете сохранить свой текущий код и добавить таймер в цикл выполнения, когда будете готовы к этому:

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)
  

Комментарии:

1. Как приостановить и возобновить этот таймер?

2. MyTimer.invalidate() отменяет его. Кроме этого, NSTimer не поддерживает никаких функций приостановки / возобновления. Вы могли бы представить себе простой подкласс, который добавляет эту поддержку, (1) отмечая время начала, (2) отмечая время паузы и (3) при возобновлении выполнения на t2-t1 короче, чем наш исходный timeInterval.

3. Синтаксис Sift 3.0 для цикла выполнения: RunLoop.current.add(MyTimer, forMode: RunLoopMode.commonModes)

4. Swift3 RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)

Ответ №2:

я использую аналогичный подход к Luke. Только предостережение для людей, которые являются пуристами «частных методов»:

НЕ делайте обратный вызов приватным в Swift.

Если вы напишете:

 private func timerCallBack(timer: NSTimer){
  

..

вы получите:

TimerCallback:]: нераспознанный селектор, отправленный экземпляру… Завершение работы приложения из-за неперехваченного исключения ‘NSInvalidArgumentException’

Комментарии:

1. Большое вам спасибо за это!

2. просто добавьте @objc в частный метод, и он также будет работать: @objc private func timerCallBack(timer: NSTimer){

3. Предложенный ответ @Lensflare правильный, вам просто нужен @objc декоратор метода.

4. да, я знаю «@objc», но мы говорим о пуристе «swift»… поэтому избегайте всех старых сделок с objc. (Мне это тоже понравилось ..)

Ответ №3:

NSTimer не планируются автоматически, если вы не используете NSTimer.scheduledTimerWithTimeInterval :

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

Ответ №4:

Как указали Drewag и Ryan, вам необходимо создать таймер по расписанию (или запланировать его самостоятельно) Проще всего создать его запланированным уже с:

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

Вам также необходимо изменить определение timerFunc (и связанного с ним селектора), чтобы принимать аргумент и заканчиваться символом ‘:’

 func timerFunc(timer:NSTimer!) {
    ...
}
  

Комментарии:

1. Вам не нужно определять его, чтобы принимать аргумент. В любом случае все в порядке.

2. @drewag хорошо, я только что вспомнил некоторое обсуждение SO о том, что оно не работает без ‘:’, поэтому я попробовал его на игровой площадке. Вам нужно ‘:’, если вы (правильно) определяете обратный вызов как принимающий аргумент. «Правильно», потому что в NSTimer документации говорится, что обратный вызов должен принимать один аргумент. «Селектор должен иметь следующую подпись: timerFireMethod: (включая двоеточие, чтобы указать, что метод принимает аргумент)». Кажется, что вы можете обойтись без : или аргумента, но я бы все же сказал, что мой ответ правильный, основанный на документации 🙂

3. аргументы также являются необязательными в IBActions и в любом другом месте, где я когда-либо определял селектор (по крайней мере, с одним аргументом). Это просто природа среды выполнения. Тот факт, что он пропустил аргумент, не имеет ничего общего с тем, почему он не вызывается. В худшем случае это выдаст исключение о том, что селектор не найден, а не сбой без сбоев.

Ответ №5:

Для 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)
  

Комментарии:

1. Использование scheduledTimer() метода автоматически добавит ваш таймер в цикл выполнения — вам не нужно добавлять его вручную.

Ответ №6:

Синтаксис Swift 3.0 для выполнения цикла:

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

Ответ №7:

Это фрагмент кода, который демонстрирует, как вызывать функцию (с задержкой) с параметром и без него.

Используйте это в новом проекте в 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 *) таймер Читать полностью -> здесь

Ответ №8:

Поскольку этот поток заставил меня попытаться самостоятельно установить таймер на цикл выполнения (что решило мою проблему), я также публикую свой конкретный случай — кто знает, может быть, это кому-то поможет. Мой таймер создается во время запуска приложения и инициализации всех объектов. Моя проблема заключалась в том, что, хотя он и планировал таймер, он все равно никогда не срабатывал. Я предполагаю, что это было так, потому scheduledTimerWithTimeInterval что во время запуска приложения таймер был установлен на другой цикл выполнения. Если я просто инициализирую таймер, а затем использую NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode) вместо него, он работает нормально.

Ответ №9:

С 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
}
  

Ответ №10:

Чтобы сделать это с помощью метода, предложенного OP, вам нужно добавить его в цикл выполнения:

 myTimer = NSTimer(timeInterval: 5.0, target: self, selector:"timerFunc", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)
  

В документации также говорится, что цель должна принимать аргумент, но она работает без него.

 func timerFireMethod(timer: NSTimer) { }