Avatar ·

Почему в Swift 4 устарело неявное @objc и как это исправить?

Всем привет! Я недавно обновил свой проект с Swift 3 на Swift 4 (использую Xcode 9 beta) и столкнулся с кучей предупреждений. Компилятор пишет что-то вроде: «The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please minimize deprecated @objc inference...» Раньше я писал код на Swift 3, и у меня работали селекторы для кнопок, target-action, а также методы, которые вызывались из Objective-C. Например, вот такой простой код работал без проблем: ```swift class MyViewController: UIViewController { @IBOutlet weak var myButton: UIButton! override func viewDidLoad() { super.viewDidLoad() myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } func buttonTapped() { print("Button tapped") } } ``` После обновления на Swift 4 я получаю предупреждение на строке с `#selector(buttonTapped)`. Я понимаю, что это связано с новыми правилами вывода `@objc`, но не до конца разбираюсь. Почему это произошло? Как правильно исправить все эти предупреждения? Нужно ли везде добавлять `@objc` вручную? И что будет, если я просто проигнорирую предупреждения? Проект большой, и вручную переписывать всё очень страшно. Буду благодарен за подробное объяснение и примеры. <<>>

Почему это произошло?

В Swift 3 компилятор автоматически добавлял атрибут @objc к методам, которые могли быть вызваны из Objective-C (например, через #selector или target-action). Это называлось «неявный вывод @objc». В Swift 4 такое поведение стало устаревшим, потому что:

  • Оно создавало много скрытого Objective-C кода, который увеличивал размер бинарника.
  • Затрудняло понимание, какие методы действительно доступны из Objective-C.
  • Мешало оптимизациям компилятора (например, инлайнингу).

В Swift 4 вы должны явно указывать @objc для методов, которые используются с #selector или вызываются из Objective-C. Исключение — методы, переопределяющие @objc методы из суперкласса (например, viewDidLoad), они наследуют атрибут автоматически.

Как исправить предупреждения?

Самый простой способ — добавить @objc перед методом. Исправленный пример из вопроса:

class MyViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }
    
    @objc func buttonTapped() {
        print("Button tapped")
    }
}

Если у вас много методов, которые нужно сделать доступными для Objective-C, вы можете использовать @objcMembers перед классом. Тогда все методы класса (кроме private) автоматически получат @objc:

@objcMembers
class MyViewController: UIViewController {
    // Все методы здесь будут @objc
    func buttonTapped() { }
    func anotherAction() { }
}

Но будьте осторожны: @objcMembers включает все методы, что может увеличить размер бинарника. Лучше использовать его только для небольших классов или на время миграции.

Что делать с большим проектом?

Для массовой миграции:

  1. Используйте фикс Xcode: В меню Edit → Convert → To Current Swift Syntax… Xcode сам добавит @objc куда нужно.
  2. Настройте уровень вывода: В настройках проекта (Build Settings) найдите Swift 3 @objc Inference и установите Default (чтобы временно подавить предупреждения) или On (для полной обратной совместимости, но это не рекомендуется).
  3. Постепенно исправляйте: Установите Swift 3 @objc Inference в On для всей сборки, затем по одному файлу переключайте на Swift 4, исправляя предупреждения.

Игнорировать предупреждения не стоит — в будущих версиях Swift (5 и выше) это станет ошибкой. Лучше сразу привыкнуть к явному указанию @objc.

<<>>

Альтернативный подход: Использование расширений и протоколов

Если вы не хотите добавлять @objc к каждому методу в классе, можно вынести Objective-C совместимые методы в отдельное расширение с атрибутом @objc:

class MyViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }
}

@objc extension MyViewController {
    func buttonTapped() {
        print("Button tapped")
    }
}

Этот подход помогает лучше структурировать код: все методы, которые должны быть видны из Objective-C, собраны в одном месте. Плюс вы не рискуете случайно сделать @objc лишние методы, как в случае с @objcMembers.

Также можно использовать протоколы с атрибутом @objc:

@objc protocol ButtonActions {
    func buttonTapped()
}

class MyViewController: UIViewController, ButtonActions {
    // реализация
}

Это полезно, если вы работаете с делегатами или кастомными target-action паттернами. Но для простых случаев достаточно добавить @objc к конкретному методу.