Avatar ·

Устарело ли использование @objc inference в Swift 4 mode? Как исправить?

Всем привет! Я обновил свой проект с Xcode 8 (Swift 3) до Xcode 9 beta (Swift 4). Сразу после открытия проекта Xcode выдал кучу предупреждений: "The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address this warning..." Я в замешательстве, потому что раньше в Swift 3 я мог просто написать метод или свойство, и оно автоматически становилось доступным для Objective-C. Например, у меня есть такой код: ```swift class MyClass: NSObject { var name: String = "" func doSomething() { print("Doing something") } } ``` В Swift 3 этот код работал без ошибок, и я мог вызывать `doSomething()` из Objective-C. Теперь Xcode 9 ругается. Мне нужно переписать весь проект? Что такое @objc inference и почему его использование устарело? Как правильно исправить эту проблему, чтобы не потерять совместимость с Objective-C? Буду благодарен за подробное объяснение с примерами. <<>>

Что такое @objc inference и почему оно устарело?

В Swift 3 компилятор автоматически добавлял атрибут @objc к любому члену класса, унаследованного от NSObject, если это было необходимо для совместимости с Objective-C. Это называлось @objc inference. Проблема в том, что такое неявное поведение увеличивало размер бинарного файла и замедляло компиляцию, так как компилятору приходилось анализировать все методы и свойства на предмет возможного использования из Objective-C.

В Swift 4 это поведение было признано устаревшим. Теперь вы должны явно указывать, какие именно элементы должны быть доступны из Objective-C, с помощью атрибута @objc. Это дает больше контроля и повышает производительность.

Как исправить проблему?

Есть несколько способов решения. Рассмотрим их на вашем примере:

class MyClass: NSObject {
    var name: String = ""
    func doSomething() {
        print("Doing something")
    }
}

Способ 1: Добавить @objc к каждому члену

Самый простой способ — явно пометить каждый метод или свойство, которое должно быть доступно из Objective-C:

class MyClass: NSObject {
    @objc var name: String = ""
    @objc func doSomething() {
        print("Doing something")
    }
}

Способ 2: Добавить @objcMembers ко всему классу

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

@objcMembers class MyClass: NSObject {
    var name: String = "" // автоматически @objc
    func doSomething() {  // автоматически @objc
        print("Doing something")
    }
}

Способ 3: Использовать расширение с @objc

Если вы хотите отделить Objective-C совместимый код от чистого Swift, можно вынести методы в расширение:

class MyClass: NSObject {
    var name: String = ""
}

@objc extension MyClass {
    func doSomething() {
        print("Doing something")
    }
}

Дополнительные рекомендации

  • Не злоупотребляйте @objcMembers. Используйте его только если класс действительно активно используется из Objective-C. Иначе вы получите тот же недостаток, что и в Swift 3 — лишний код.
  • Проверьте настройки проекта. В Xcode 9+ в настройках проекта (Build Settings) есть параметр Swift 3 @objc Inference. По умолчанию он стоит в Default (для Swift 4 — это "отключено"). Вы можете временно установить его в On, чтобы подавить предупреждения, но это не рекомендуется — лучше сразу исправить код.
  • Используйте #selector. Если вы используете селекторы (например, для target-action), обязательно добавьте @objc к методу, иначе получите ошибку компиляции.

В итоге, вам не нужно переписывать весь проект. Достаточно пройтись по предупреждениям Xcode и добавить @objc в нужные места. Это займет немного времени, но сделает код более явным и эффективным.

<<>>

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

Если ваш класс должен быть совместим с Objective-C только для определенного набора методов, вы можете определить протокол, унаследованный от NSObjectProtocol, и пометить его @objc. Затем реализовать этот протокол в классе. Это дает более четкое разделение ответственности:

@objc protocol MyObjcCompatible {
    func doSomething()
}

class MyClass: NSObject, MyObjcCompatible {
    var name: String = ""
    
    @objc func doSomething() {
        print("Doing something")
    }
}

Обратите внимание: даже при использовании протокола, реализующие методы все равно нужно помечать @objc (если они не входят в @objcMembers класс).

Еще один совет: Автоматическая миграция

Xcode 9+ предлагает автоматическое исправление предупреждений. Кликните на желтый треугольник слева от строки с предупреждением, и выберите "Add @objc" или "Add @objcMembers". Это сработает для простых случаев, но для сложных иерархий лучше проверить вручную.

Также рекомендую использовать #warning или FIXME для отслеживания мест, где вы временно оставили @objc, чтобы позже пересмотреть необходимость совместимости с Objective-C.