Avatar ·

Как использовать @selector() в Swift для NSTimer и других методов?

Привет всем! Я новичок в Swift и пытаюсь разобраться с селекторами. У меня есть опыт работы с Objective-C, где я часто использовал @selector() для передачи методов в таймеры или обработчики событий. Например, я хочу создать простой таймер, который вызывает метод каждую секунду. Вот мой код на Swift: ```swift import UIKit class ViewController: UIViewController { var timer: Timer? override func viewDidLoad() { super.viewDidLoad() // Пытаюсь создать таймер, но не знаю, как правильно написать селектор timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } func updateTimer() { print("Таймер сработал!") } } ``` Но Xcode выдает ошибку: "Argument of '#selector' refers to instance method 'updateTimer()' that is not exposed to Objective-C". Я не понимаю, что это значит. В Objective-C я просто писал @selector(updateTimer) и всё работало. Почему в Swift это не работает? Нужно ли добавлять какие-то модификаторы к методу? Или селекторы вообще по-другому работают в Swift? Подскажите, пожалуйста, как правильно передать метод в селектор для таймера. Также интересует, как быть, если метод принимает параметры, например, updateTimer(timer: Timer). Заранее спасибо за помощь! <<>>

Проблема с @selector в Swift и её решение

Здравствуйте! Ваша ошибка связана с тем, что Swift — язык, который по умолчанию не экспортирует методы в Objective-C runtime. Селекторы — это механизм из Objective-C, поэтому для их использования в Swift нужно явно указать, что метод доступен для Objective-C. Это делается с помощью атрибута @objc.

Вот исправленный код для вашего таймера:

import UIKit

class ViewController: 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("Таймер сработал!")
    }
}

Ключевые моменты

  • Атрибут @objc — обязателен для любого метода, который вы хотите использовать с селектором. Он сообщает компилятору, что метод должен быть виден из Objective-C runtime.
  • Синтаксис #selector — в Swift используется #selector(имяМетода) вместо @selector(). Это более безопасно, так как компилятор проверяет, существует ли метод.
  • Наследование от NSObject — если ваш класс наследуется от NSObject (например, UIViewController наследует), то все методы с @objc автоматически становятся доступными. Для чистых Swift-классов нужно явно добавлять @objc или наследовать от NSObject.

Работа с методами, принимающими параметры

Если ваш метод принимает параметры, например, сам таймер, то селектор записывается так:

@objc func updateTimer(timer: Timer) {
    print("Таймер сработал! \(timer)")
}

// В viewDidLoad:
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer(timer:)), userInfo: nil, repeats: true)

Обратите внимание на #selector(updateTimer(timer:)) — мы указываем имя метода с параметром. Это обязательно, если метод принимает аргументы.

Альтернативный синтаксис для селекторов с несколькими параметрами

Если метод принимает несколько параметров, например:

@objc func handleTap(sender: UITapGestureRecognizer, event: UIEvent) {
    // обработка
}

Тогда селектор будет:

#selector(handleTap(sender:event:))

Когда @objc не нужен

В некоторых случаях @objc добавляется автоматически:

  • Если метод переопределяет метод из Objective-C (например, viewDidLoad).
  • Если метод реализует протокол Objective-C (например, UITableViewDataSource).
  • Если класс наследуется от NSObject и метод помечен @IBAction или @IBOutlet.

В вашем случае с таймером, просто добавьте @objc перед методом, и всё заработает. Удачи в изучении Swift!

<<>>

Использование замыканий вместо селекторов

В современном Swift (начиная с версии 3.0) можно избежать использования селекторов, если вы работаете с таймерами. Вместо метода с @objc вы можете передать замыкание (closure) непосредственно в таймер. Это более чистый и Swift-стильный подход, который не требует взаимодействия с Objective-C runtime.

Вот как это выглядит:

import UIKit

class ViewController: UIViewController {
    var timer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
            // Используем weak self для предотвращения утечек памяти
            self?.updateTimer(timer: timer)
        }
    }
    
    func updateTimer(timer: Timer) {
        print("Таймер сработал! \(timer)")
    }
}

Преимущества этого подхода:

  • Не нужно добавлять @objc к методам.
  • Нет риска опечаток в имени селектора — компилятор проверяет типы.
  • Лучшая производительность, так как не задействуется динамическая диспетчеризация Objective-C.
  • Более читаемый код для разработчиков, знакомых с замыканиями.

Обратите внимание на [weak self] — это важно для предотвращения циклических ссылок, так как таймер удерживает замыкание, а замыкание удерживает self. Если ваш метод не использует self, можно опустить weak self.

Этот подход работает для всех версий Swift начиная с 3.0. Если вам нужно поддерживать более старые версии (Swift 2.x), то придётся использовать селекторы с @objc.