Метод не - '@ objc' не удовлетворяет необязательному требованию протокола '@objc'

105

Обзор:

  • У меня есть протокол P1, который обеспечивает реализацию по умолчанию одной из дополнительных функций Objective-C.
  • Когда я предоставляю реализацию дополнительной функции по умолчанию, появляется предупреждение

Предупреждение компилятора:

Non-'@objc' method 'presentationController(_:viewControllerForAdaptivePresentationStyle:)' does not satisfy optional requirement of '@objc' protocol 'UIAdaptivePresentationControllerDelegate'

Версия:

  • Swift: 3
  • Xcode: 8 (публичный выпуск)

Были предприняты попытки:

  • Пытался добавить, @objcно не помогает

Вопрос:

  • Как я решил это?
  • Есть ли обходной путь?

Код:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}


class A : UIViewController, P1 {

}
user1046037
источник
У вас самая новая версия Xcode? Если я удалю, у меня не будет ошибок@objc
Qbyte
Я использую Xcode 8 (последняя общедоступная версия). Ошибки нет, но будет предупреждение
user1046037

Ответы:

184

Хотя я думаю, что могу ответить на ваш вопрос, это не тот ответ, который вам понравится.

TL; DR: @objc функции в настоящее время могут отсутствовать в расширениях протокола. Вместо этого вы можете создать базовый класс, хотя это не идеальное решение.

Расширения протокола и Objective-C

Во-первых, этот вопрос / ответ ( Может ли метод Swift, определенный в расширениях для протоколов, доступных в Objective-c ), по-видимому, предполагает, что из-за того, как расширения протокола отправляются под капотом, методы, объявленные в расширениях протокола, не видны для objc_msgSend()функции и поэтому они не видны для кода Objective-C. Поскольку метод, который вы пытаетесь определить в своем расширении, должен быть видим для Objective-C (чтобы его UIKitможно было использовать), он кричит на вас, что вы не включаете его @objc, но как только вы его включаете, он кричит на вас, потому что@objc он не разрешен в расширения протокола. Вероятно, это связано с тем, что расширения протокола в настоящее время не могут быть видны Objective-C.

Мы также можем видеть, что сообщение об ошибке после добавления @objc состояний «@objc может использоваться только с членами классов, протоколами @objc и конкретными расширениями классов». Это не класс; расширение протокола @objc - это не то же самое, что в самом определении протокола (то есть в требованиях), а слово «конкретный» предполагает, что расширение протокола не считается расширением конкретного класса.

Обходной путь

К сожалению, это практически полностью мешает вам использовать расширения протокола, когда реализации по умолчанию должны быть видимы для платформ Objective-C. Сначала я подумал, что, возможно, @objcэто не разрешено в вашем расширении протокола, потому что компилятор Swift не может гарантировать, что соответствующие типы будут классами (даже если вы специально указали UIViewController). Итак, я поставил classтребование P1. Это не сработало.

Возможно, единственный обходной путь здесь - просто использовать базовый класс вместо протокола, но это, очевидно, не совсем идеально, потому что класс может иметь только один базовый класс, но соответствовать нескольким протоколам.

Если вы решите пойти по этому пути, примите во внимание этот вопрос ( метод необязательного протокола Swift 3 ObjC, не вызываемый в подклассе ). Похоже, что еще одна текущая проблема в Swift 3 заключается в том, что подклассы не наследуют автоматически дополнительные реализации требований протокола своего суперкласса. Ответ на этот вопрос требует специальной адаптации, @objcчтобы обойти это.

Сообщение о проблеме

Я думаю, что это уже обсуждается среди тех, кто работает над проектами с открытым исходным кодом Swift, но вы можете быть уверены, что они знают, используя Apple Bug Reporter , который, скорее всего, в конечном итоге дойдет до Swift Core Team, или репортера ошибок Swift . Однако любой из них может посчитать вашу ошибку слишком широкой или уже известной. Команда Swift может также рассмотреть то, что вы ищете, как новую языковую функцию, и в этом случае вам следует сначала проверить списки рассылки .

Обновить

В декабре 2016 года об этой проблеме было сообщено сообществу Swift. Проблема по-прежнему помечена как открытая со средним приоритетом, но был добавлен следующий комментарий:

Это задумано. Невозможно добавить реализацию метода для каждого пользователя, поскольку расширение может быть добавлено после соответствия протоколу. Я полагаю, мы могли бы разрешить это, если бы расширение было в том же модуле, что и протокол.

Однако, поскольку ваш протокол находится в том же модуле, что и ваше расширение, вы можете сделать это в будущей версии Swift.

Обновление 2

В феврале 2017 года один из членов основной группы Swift официально закрыл эту проблему как «Не буду» со следующим сообщением:

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

Расширение NSObjectили даже UIViewControllerне приведет к тому, что вы хотите, но, к сожалению, не похоже, что это станет возможным.

В (очень) долгосрочном будущем мы, возможно, сможем полностью отказаться от использования @objcметодов, но это время, скорее всего, не наступит в ближайшее время, поскольку фреймворки Какао в настоящее время не написаны на Swift (и не может быть, пока у него не будет стабильного ABI) .

Обновление 3

По состоянию на осень 2019 года это становится менее серьезной проблемой, потому что все больше и больше фреймворков Apple пишется на Swift. Например, если вы используете SwiftUIвместо UIKit, вы полностью обойдете проблему, потому @objcчто в этом никогда не будет необходимости при обращении к SwiftUIметоду.

Фреймворки Apple, написанные на Swift, включают:

  • SwiftUI
  • RealityKit
  • Объединить
  • CryptoKit

Можно было бы ожидать, что эта модель сохранится со временем, поскольку Swift официально является ABI и стабильным модулем в Swift 5.0 и 5.1, соответственно.

Мэтью Симэн
источник
1
@ user1046037 Я тоже, так как вижу, что сталкиваюсь с этой проблемой много раз в будущем.
Мэтью Симан
2
Вы правы, ваш ответ остается в силе, несмотря на то, что на данный момент Swift 4нет другой альтернативы.
user1046037
2
У меня был точно такой же код, который работал у меня некоторое время, но ломался в более поздней версии Xcode. Это довольно раздражает. В протоколах Objective-C так много дополнительных методов.
Departamento B,
0

Я столкнулся с этим только после включения «стабильности модуля» (включения «Сборка библиотек для распространения») в используемой мной быстрой среде.

У меня было что-то вроде этого:

class AwesomeClass: LessAwesomeClass {
...
}

extension AwesomeClass: GreatDelegate {
  func niceDelegateFunc() {
  }
}

Функция в расширении имела следующие ошибки:

  • Метод экземпляра '@objc' в расширении подкласса 'LessAwesomeClass' требует iOS 13.0.0

  • Не - '@ objc' метод niceDelegateFunc 'не удовлетворяет требованиям протокола' @objc 'GreatDelegate

Перемещение функций в класс, а не в расширение, решило проблему.

CMash
источник
0

Вот еще одно решение. Я тоже столкнулся с этой проблемой и пока не могу переключиться с UIKit на SwiftUI. Перемещение реализаций по умолчанию в общий базовый класс тоже не было вариантом для меня. Мои реализации по умолчанию были довольно обширными, поэтому я действительно не хотел дублировать весь этот код. Обходной путь, который я в итоге использовал, заключался в использовании функций-оберток в протоколе, а затем просто вызов этих функций из каждого класса. Не очень красиво, но может быть лучше, чем альтернатива, в зависимости от ситуации. Тогда ваш код будет выглядеть примерно так:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {
}

extension P1 where Self : UIViewController {
    func wrapPresentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}

class A : UIViewController, P1 {
    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return wrapPresentationController(controller, viewControllerForAdaptivePresentationStyle: style)
    }
}
рене
источник