Как использовать Timer (NSTimer) в Swift: создание, остановка и повторение таймера
1. Основы Timer в Swift
Класс Timer (ранее NSTimer) — это основной способ выполнения кода с задержкой или с повторением. В Swift 5+ рекомендуется использовать современный API с замыканиями, который безопаснее и читабельнее.
2. Создание и остановка таймера (правильный способ)
Чтобы иметь возможность остановить таймер, нужно сохранить ссылку на него. Вот исправленный пример с учётом вашей задачи:
class ViewController: UIViewController {
var seconds = 10
var timer: Timer?
func startTimer() {
// Создаём таймер с замыканием (closure) – современный способ
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
guard let self = self else { return }
self.seconds -= 1
print(self.seconds)
if self.seconds == 0 {
self.stopTimer()
}
}
}
func stopTimer() {
timer?.invalidate() // Останавливаем таймер
timer = nil // Освобождаем ссылку
print("Таймер остановлен")
}
// Не забываем остановить таймер при уходе с экрана
deinit {
timer?.invalidate()
}
}
3. Почему нужно использовать [weak self]
Таймер удерживает сильную ссылку на объект (self), а объект удерживает сильную ссылку на таймер. Это создаёт циклическую ссылку (retain cycle), из-за которой объект никогда не освободится из памяти. [weak self] разрывает этот цикл. Если не использовать weak, ваш ViewController никогда не вызовет deinit.
4. Таймер без повторения (однократный срабатывание)
Просто передайте repeats: false:
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { timer in
print("Прошло 5 секунд")
}
5. Таймер с селектором (для совместимости)
Если вы используете селектор (например, для вызова метода из Objective-C рантайма), обязательно добавляйте @objc и сохраняйте ссылку:
class ViewController: UIViewController {
var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateTimer),
userInfo: nil,
repeats: true
)
}
@objc func updateTimer() {
print("Тик")
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
}
6. Полезные советы
- Всегда invalidate() таймер, когда он больше не нужен — в
viewDidDisappearилиdeinit. - Не используйте таймеры с высокой частотой (менее 0.1 секунды) — для анимаций используйте
CADisplayLink. - Таймеры не работают в фоновом режиме приложения — для фоновых задач используйте
Background Tasks. - Точность:
Timerне гарантирует миллисекундную точность, особенно при большой нагрузке.
7. Полный рабочий пример
import UIKit
class TimerViewController: UIViewController {
@IBOutlet weak var label: UILabel!
private var timer: Timer?
private var remainingSeconds = 10
override func viewDidLoad() {
super.viewDidLoad()
startCountdown()
}
private func startCountdown() {
label.text = "\(remainingSeconds)"
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.remainingSeconds -= 1
self.label.text = "\(self.remainingSeconds)"
if self.remainingSeconds == 0 {
self.timer?.invalidate()
self.timer = nil
self.label.text = "Время вышло!"
}
}
}
deinit {
timer?.invalidate()
}
}
<<Альтернативный подход: DispatchSourceTimer (более точный и гибкий)
Если вам нужен таймер с высокой точностью или возможностью приостановки/возобновления, используйте DispatchSourceTimer из Grand Central Dispatch. Он работает на уровне системы и не зависит от RunLoop.
class PrecisionTimer {
private var timer: DispatchSourceTimer?
func start(interval: TimeInterval, repeats: Bool, handler: @escaping () -> Void) {
let queue = DispatchQueue.global(qos: .background)
timer = DispatchSource.makeTimerSource(queue: queue)
let deadline = DispatchTime.now()
let repeatingInterval = repeats ? interval : .never
timer?.schedule(deadline: deadline, repeating: repeatingInterval, leeway: .nanoseconds(0))
timer?.setEventHandler { [weak self] in
DispatchQueue.main.async {
handler()
}
}
timer?.resume()
}
func stop() {
timer?.cancel()
timer = nil
}
deinit {
stop()
}
}
// Использование:
let timer = PrecisionTimer()
timer.start(interval: 1.0, repeats: true) {
print("Тик от DispatchSourceTimer")
}
Плюсы и минусы
- + Высокая точность (до наносекунд)
- + Можно приостановить (
suspend()) и возобновить (resume()) - + Работает в фоновых потоках без RunLoop
- - Требует ручного управления памятью (cancel/resume)
- - Сложнее в использовании для простых задач
Для большинства случаев Timer.scheduledTimer с замыканием — оптимальный выбор. DispatchSourceTimer стоит применять только когда нужна особая точность или работа в фоне.