Что такое @selector() в Swift и как его использовать с NSTimer?
Проблема селекторов в Swift
В Swift селекторы работают иначе, чем в Objective-C. Основная причина вашей ошибки — метод updateTimer: не помечен как доступный для использования с Objective-C runtime. В Swift методы по умолчанию не видны из Objective-C, а селекторы — это механизм именно Objective-C.
Решение: атрибут @objc
Чтобы метод можно было использовать с селектором, его нужно пометить атрибутом @objc. Вот правильный код:
class MyViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Правильный способ создания селектора
timer = Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: #selector(updateTimer),
userInfo: nil,
repeats: true)
}
@objc func updateTimer() {
print("Таймер сработал!")
}
deinit {
timer?.invalidate()
}
}
Ключевые моменты:
- #selector вместо @selector — в Swift используется синтаксис
#selector(имяМетода) - Атрибут @objc — обязателен для любого метода, который будет использоваться как селектор
- Параметры метода — если метод принимает параметры, их нужно указывать в селекторе иначе
Селекторы с параметрами
Если ваш метод принимает параметр (например, сам таймер), синтаксис меняется:
@objc func updateTimer(_ timer: Timer) {
print("Таймер сработал: \(timer.timeInterval)")
}
// Создание селектора
let selector = #selector(updateTimer(_:)) // Обратите внимание на (_:)
timer = Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: selector,
userInfo: nil,
repeats: true)
Почему это безопаснее, чем Selector("updateTimer:")
Использование #selector даёт проверку на этапе компиляции:
- Если метода не существует — вы получите ошибку компиляции
- Если метод не помечен как
@objc— компилятор тоже укажет на ошибку - Если измените сигнатуру метода — компилятор потребует обновить селектор
Строковая инициализация Selector("updateTimer:") не даёт такой защиты и может привести к крашу в рантайме, если метод переименуют или удалят.
Альтернатива: замыкания вместо селекторов
В современном Swift для таймеров можно использовать замыкания, что ещё безопаснее:
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
print("Таймер сработал!")
// self?.updateTimer(timer)
}
Этот подход не требует @objc и полностью типобезопасен.
Дополнение: наследование от NSObject
Ещё один важный момент — если ваш класс не наследуется от NSObject или его подкласса (например, UIViewController), то @objc может не сработать. В Swift методы должны быть доступны в Objective-C runtime, а это возможно только для классов, унаследованных от NSObject.
Пример для обычного Swift-класса:
// Неправильно — класс не наследует NSObject
class MyTimerHandler {
@objc func fire() { }
}
// Правильно
class MyTimerHandler: NSObject {
@objc func fire() {
print("Timer fired")
}
}
// Использование
let handler = MyTimerHandler()
let timer = Timer.scheduledTimer(timeInterval: 1.0,
target: handler,
selector: #selector(MyTimerHandler.fire),
userInfo: nil,
repeats: true)
Совет для UIKit
В большинстве случаев вы работаете с UIViewController, UIView и другими классами UIKit, которые уже наследуют NSObject. Поэтому достаточно просто добавить @objc к методу. Если же вы создаёте свой класс с нуля, не забудьте унаследовать его от NSObject.
Также обратите внимание: начиная с Swift 4, если класс помечен как @objcMembers, все его методы автоматически становятся доступными для селекторов:
@objcMembers class MyViewController: UIViewController {
func updateTimer() { } // Уже доступен для #selector
}
Но это избыточно для большинства случаев — лучше явно помечать только нужные методы.