Делайте что-нибудь каждые x минут в Swift

113

Как я могу запускать функцию каждую минуту? В JavaScript я могу сделать что-то вроде setInterval, существует ли что-то подобное в Swift?

Требуемый результат:

Привет, мир, раз в минуту ...

JOSEFtw
источник
Обновлено для Swift 2: Swift Timer
JamesG

Ответы:

171
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

func sayHello() 
{
    NSLog("hello World")
}

Не забудьте импортировать Foundation.

Swift 4:

 var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)

 @objc func sayHello() 
 {
     NSLog("hello World")
 }
Unheilig
источник
1
но что, если вы хотите переключаться между представлениями? Код остановится правильно?
Cing
5
@Cing зависит от того, о чем идет речь.
Antzi
1
Не забывайте, что NSTimerэто сохраняет свою цель, поэтому с этой настройкой, если helloWorldTimerэто свойство self, у вас есть цикл сохранения, в котором selfсохраняется helloWorldTimerи helloWorldTimerсохраняется self.
MANIAK_dobrii
@MANIAK_dobrii Не могли бы вы также прокомментировать, как разорвать цикл сохранения? Если ВК отклонен, но таймер не отменен, цикл все еще работает? Значит, вы должны одновременно отменить таймер, а затем закрыть представление, чтобы разорвать цикл?
Dave G
1
Для Swift 4 метод, для которого вы хотите получить селектор, должен быть доступен для Objective-C, поэтому атрибут @objc должен быть добавлен в объявление метода. например, `` @objc func sayHello () {} `` ``
Шамим Хоссейн,
136

Если нацелена на iOS версии 10 и выше, вы можете использовать блочное представление Timer, что упрощает потенциальные циклы сильных ссылок, например:

weak var timer: Timer?

func startTimer() {
    timer?.invalidate()   // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
    timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
        // do something here
    }
}

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

// if appropriate, make sure to stop your timer in `deinit`

deinit {
    stopTimer()
}

Хотя Timerв целом это лучше, для полноты картины я должен отметить, что вы также можете использовать таймер отправки, который полезен для планирования таймеров в фоновых потоках. С таймерами отправки, поскольку они основаны на блоках, они позволяют избежать некоторых проблем с сильным ссылочным циклом со старым target/ selectorшаблономTimer , если вы используете weakссылки.

Так:

var timer: DispatchSourceTimer?

func startTimer() {
    let queue = DispatchQueue(label: "com.domain.app.timer")  // you can also use `DispatchQueue.main`, if you want
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer!.schedule(deadline: .now(), repeating: .seconds(60))
    timer!.setEventHandler { [weak self] in
        // do whatever you want here
    }
    timer!.resume()
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

deinit {
    self.stopTimer()
}

Для получения дополнительной информации см Создание таймера раздел Отправка Источник примеров в источниках отправки секции параллелизмом Руководство по программированию.


Для Swift 2 см. Предыдущую версию этого ответа .

Роб
источник
как бы вы выбрали таймер однократного выполнения в рамках этого подхода?
Хуан Боэро
@JuanPabloBoero - Я бы не стал использовать этот подход в случае однократного пожара. Вы бы использовали dispatch_after. Или неповторяющийся NSTimer.
Роб
@Rob У меня есть метка счетчика на экране, которая обновляется каждую секунду функцией, и я использую приведенный выше код для увеличения счетчика и обновления текста метки. Но проблема, с которой я столкнулся, заключается в том, что моя переменная счетчика обновляется каждую секунду, но на экране не отображается последнее значение счетчика в моем UILabel.
Нагендра Рао,
Рао, это полезно для выполнения некоторых действий в фоновом потоке. Но ваши обновления пользовательского интерфейса должны происходить в основной очереди. Итак, либо запланируйте это в основном потоке, либо, по крайней мере, отправьте обновления пользовательского интерфейса обратно в основной поток.
Роб
1
Этот вопрос не относится к комментариям к этому ответу. Удалите свои комментарии выше и разместите свой вопрос. Но, короче говоря, вы обычно не заставляете приложение работать в фоновом режиме, а только заставляете его выглядеть так, как было. См. Stackoverflow.com/a/31642036/1271826 .
Роб
17

Вот обновление NSTimerответа для Swift 3 (в котором NSTimerбыл переименован в Timer) с использованием замыкания, а не именованной функции:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
Джон Т
источник
6
это только для iOS 10.
Glenn
пожалуйста, не
публикуйте
13

Если вы можете позволить себе некоторое время дрейфовать, вот простое решение, выполняющее некоторый код каждую минуту:

private func executeRepeatedly() {
    // put your code here

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
        self?.executeRepeatedly()
    }
}

Просто запустите executeRepeatedly()один раз, и он будет выполняться каждую минуту. Выполнение останавливается, когда объект-владелец ( self) освобождается. Вы также можете использовать флаг, чтобы указать, что выполнение должно быть остановлено.

algrid
источник
Это решение намного удобнее, чем иметь все эти таймеры, добавлять их в циклы выполнения, делать их недействительными… Круто!
julia_v
это кажется правильным решением, но оно создает цикл сохранения, когда я использую его с вызовом веб-службы ...
jayant rawat
11

Вы можете использовать Timer(swift 3)

var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)

В selector () вы вводите имя своей функции

Bas
источник
Как это сделать в Swift 3?
bibscy
1
use Timer... NSTimerбыл переименован
Joseph
7

В Swift 3.0 был произведен рефакторинг GCD:

let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)

timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
    NSLog("Hello World")
}
timer.resume()

Это особенно полезно, когда вам нужно выполнить отправку в определенной очереди. Кроме того, если вы планируете использовать это для обновления пользовательского интерфейса, я предлагаю проверить CADisplayLink, синхронизируется ли он с частотой обновления графического процессора.

Жестяная банка
источник