Метод "@ objc" не відповідає необов'язковій вимозі протоколу "@objc"


104

Огляд:

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

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

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

Версія:

  • Швидкий: 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 {

}

Чи є у вас найновіша версія Xcode? Я не отримую жодних помилок, якщо @objc
видаляю

Я використовую Xcode 8 (остання публічна версія). Помилки немає, але буде попередження
користувач1046037

Відповіді:


183

Хоча я думаю, що можу відповісти на ваше запитання, це вам не сподобається відповідь.

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

Розширення протоколу та ціль-C

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

Ми також можемо побачити, що повідомлення про помилку, коли ми додаємо, @objcконстатує "@objc можна використовувати лише з членами класів, протоколами @objc та конкретними розширеннями класів." Це не клас; розширення на протокол @objc - це не те саме, що в самому визначенні протоколу (тобто у вимогах), а слово "конкретний" припускає, що розширення протоколу не вважається конкретним розширенням класу.

Обхід

На жаль, це в значній мірі повністю заважає використовувати розширення протоколів, коли реалізації за замовчуванням повинні бути видні рамкам Objective-C. Спочатку я подумав, що, можливо, @objcце не було дозволено у вашому розширенні протоколу, оскільки Swift Compiler не міг гарантувати, що відповідні типи будуть класами (навіть якщо ви конкретно вказали UIViewController). Тому я ставлю classвимогу P1. Це не вийшло.

Мабуть, єдине вирішення - просто використовувати базовий клас замість протоколу, але це, очевидно, не зовсім ідеально, оскільки клас може мати лише один базовий клас, але відповідати декільком протоколам.

Якщо ви вирішили пройти цей маршрут, будь ласка, врахуйте це питання ( Swift 3 ObjC Необов’язковий протокол, метод не викликається в підкласі ). Очевидно, що ще одна актуальна проблема у Swift 3 полягає в тому, що підкласи не успадковують автоматично необов'язкові реалізації протокольних вимог їхнього суперкласу. Відповідь на ці запитання використовує спеціальну адаптацію, @objcщоб обійти її.

Повідомлення про випуск

Я думаю, що це вже обговорюється серед тих, хто працює над проектами з відкритим кодом Swift, але ви можете бути впевнені, що вони знають це, використовуючи Apple Bug Reporter , який, можливо, врешті-решт пробиться до основної команди Swift , або репортера помилок Swift . Проте, будь-яка з цих помилок може визнати вашу помилку занадто широкою або вже відомою. Команда Swift також може розглянути те, що ви шукаєте, як нову мовну функцію, і в цьому випадку спочатку слід ознайомитися зі списками розсилки .

Оновлення

У грудні 2016 року про це питання було повідомлено спільноті Swift. Випуск все ще позначений як відкритий із середнім пріоритетом, але додано наступний коментар:

Це призначено. Немає можливості додати реалізацію методу до кожного прийнятого, оскільки розширення може бути додане після відповідності протоколу. Я думаю, ми могли б дозволити це, якщо розширення буде в тому ж модулі, що і протокол.

Оскільки ваш протокол знаходиться в тому ж модулі, що і ваше розширення, ви, можливо, зможете це зробити в майбутній версії Swift.

Оновлення 2

У лютому 2017 року цей випуск офіційно закрили як "Не буду робити" один із членів Команди Swift Core із таким повідомленням:

Це навмисно: розширення протоколу не можуть вводити точки входу @objc через обмеження часу виконання Objective-C. Якщо ви хочете додати точки входу @objc до NSObject, продовжте NSObject.

Розширення NSObjectабо навіть UIViewControllerне виконають саме те, що ви хочете, але це, на жаль, не виглядає так, що стане можливим.

У (дуже) довгостроковому майбутньому ми, можливо, зможемо повністю усунути залежність від @objcметодів, але цей час, швидше за все, не настане незабаром, оскільки какаові рамки наразі не написані в Swift (і не можуть бути, поки у нього не буде стабільної ABI) .

Оновлення 3

Станом на осінь 2019 року це стає меншою проблемою, оскільки в Swift пишеться все більше рамок Apple. Наприклад, якщо ви використовуєте SwiftUIзамість цього UIKit, ви усунете проблему цілком, тому @objcщо ніколи не знадобиться при посиланні на SwiftUIметод.

Рамки Apple, написані на Swift, включають:

  • SwiftUI
  • RealityKit
  • Об’єднайте
  • CryptoKit

Можна було б очікувати, що ця модель продовжиться з часом, коли Swift офіційно є ABI та модулем стабільним на Swift 5.0 та 5.1 відповідно.


1
@ user1046037 Я також добре, тому що я можу бачити себе в цьому питанні багато разів у майбутньому розвитку.
Меттью Моряк

2
Ви правильні, ваша відповідь все ще гарна, незважаючи на Swift 4жодну іншу альтернативу на даний момент.
користувач1046037

1
У мене був такий самий код, який працював на мене деякий час, але зламався в подальшому випуску Xcode. Це досить набридливо. У протоколах Objective-C існує стільки необов'язкових методів.
Департамент Б

0

Я просто зіткнувся з цим після ввімкнення „стабільності модуля” (увімкнення „Збірка бібліотек для розповсюдження”) у швидкій основі, яку я використовую.

У мене було щось подібне:

class AwesomeClass: LessAwesomeClass {
...
}

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

У функції в розширенні були такі помилки:

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

  • Метод "@ objc" "niceDelegateFunc" не задовольняє вимогу протоколу "@objc" "GreatDelegate"

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


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)
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.