Для Swift 3.0,
Ответ Тихонва мало что объясняет. Здесь добавляет немного моего понимания.
Для краткости, вот код. Это РАЗНОЕ от кода Тихонва в том месте, где я создаю таймер. Я создаю таймер с помощью конструктора и добавляю его в цикл. Я думаю, что функция scheduleTimer добавит таймер в RunLoop основного потока. Так что лучше создать таймер с помощью конструктора.
class RunTimer{
let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent)
let timer: Timer?
private func startTimer() {
// schedule timer on background
queue.async { [unowned self] in
if let _ = self.timer {
self.timer?.invalidate()
self.timer = nil
}
let currentRunLoop = RunLoop.current
self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true)
currentRunLoop.add(self.timer!, forMode: .commonModes)
currentRunLoop.run()
}
}
func timerTriggered() {
// it will run under queue by default
debug()
}
func debug() {
// print out the name of current queue
let name = __dispatch_queue_get_label(nil)
print(String(cString: name, encoding: .utf8))
}
func stopTimer() {
queue.sync { [unowned self] in
guard let _ = self.timer else {
// error, timer already stopped
return
}
self.timer?.invalidate()
self.timer = nil
}
}
}
Создать очередь
Сначала создайте очередь, чтобы таймер работал в фоновом режиме, и сохраните эту очередь как свойство класса, чтобы использовать его для таймера остановки. Я не уверен, нужно ли нам использовать одну и ту же очередь для запуска и остановки, потому что я сделал это потому, что увидел предупреждение здесь .
Класс RunLoop обычно не считается поточно-ориентированным и
его методы должны вызываться только в контексте текущего
нить. Никогда не пытайтесь вызывать методы объекта RunLoop.
работает в другом потоке, так как это может привести к неожиданным
Результаты.
Поэтому я решил сохранить очередь и использовать ту же очередь для таймера, чтобы избежать проблем с синхронизацией.
Также создайте пустой таймер и также сохраните его в переменной класса. Сделайте его необязательным, чтобы вы могли остановить таймер и установить его на ноль.
class RunTimer{
let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent)
let timer: Timer?
}
Таймер запуска
Чтобы запустить таймер, сначала вызовите async из DispatchQueue. Затем рекомендуется сначала проверить, запущен ли таймер. Если переменная таймера не равна nil, то invalidate () ее и установите в ноль.
Следующий шаг - получить текущий RunLoop. Поскольку мы сделали это в созданном нами блоке очереди, он получит RunLoop для фоновой очереди, которую мы создали ранее.
Создать таймер. Здесь вместо использования scheduleTimer мы просто вызываем конструктор timer и передаем ему любое свойство для таймера, например timeInterval, target, selector и т. Д.
Добавить созданный таймер в RunLoop. Запустите его.
Вот вопрос о запуске RunLoop. Согласно приведенной здесь документации, он говорит, что фактически начинает бесконечный цикл, который обрабатывает данные из входных источников и таймеров цикла выполнения.
private func startTimer() {
// schedule timer on background
queue.async { [unowned self] in
if let _ = self.timer {
self.timer?.invalidate()
self.timer = nil
}
let currentRunLoop = RunLoop.current
self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true)
currentRunLoop.add(self.timer!, forMode: .commonModes)
currentRunLoop.run()
}
}
Таймер запуска
Реализуйте функцию как обычно. Когда эта функция вызывается, она вызывается по умолчанию в очереди.
func timerTriggered() {
// under queue by default
debug()
}
func debug() {
let name = __dispatch_queue_get_label(nil)
print(String(cString: name, encoding: .utf8))
}
Вышеуказанная функция отладки используется для распечатки имени очереди. Если вы когда-нибудь беспокоитесь о том, запущен ли он в очереди, вы можете позвонить ему, чтобы проверить.
Таймер остановки
Остановить таймер легко, вызовите validate () и установите переменную таймера, хранящуюся в классе, в ноль.
Здесь я снова запускаю его в очереди. Из-за этого предупреждения я решил запустить весь связанный с таймером код в очереди, чтобы избежать конфликтов.
func stopTimer() {
queue.sync { [unowned self] in
guard let _ = self.timer else {
// error, timer already stopped
return
}
self.timer?.invalidate()
self.timer = nil
}
}
Вопросы, связанные с RunLoop
Я как-то немного запутался, нужно ли нам вручную останавливать RunLoop или нет. Согласно документации здесь, кажется, что, когда нет таймеров, прикрепленных к нему, то он немедленно выйдет. Поэтому, когда мы останавливаем таймер, он должен существовать сам. Однако в конце этого документа также сказано:
удаление всех известных входных источников и таймеров из цикла выполнения не является
гарантировать, что цикл выполнения завершится. macOS может установить и удалить
дополнительные входные источники по мере необходимости для обработки запросов, направленных на
поток получателя. Таким образом, эти источники могут предотвратить цикл выполнения
с выхода.
Я испробовал приведенное ниже решение, предоставленное в документации, для гарантии завершения цикла. Однако таймер не срабатывает после того, как я изменил .run () на приведенный ниже код.
while (self.timer != nil && currentRunLoop.run(mode: .commonModes, before: Date.distantFuture)) {};
Я думаю, что было бы безопасно использовать просто .run () на iOS.Поскольку в документации говорится, что macOS устанавливается и удаляет дополнительные входные источники, необходимые для обработки запросов, направленных на поток получателя.Так что iOS может быть в порядке.