Metoda inna niż „@ objc” nie spełnia opcjonalnego wymagania protokołu „@objc”

104

Przegląd:

  • Mam protokół P1, który zapewnia domyślną implementację jednej z opcjonalnych funkcji Objective-C.
  • Kiedy podam domyślną implementację opcjonalnej funkcji, pojawia się ostrzeżenie

Ostrzeżenie kompilatora:

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

Wersja:

  • Szybki: 3
  • Xcode: 8 (wydanie publiczne)

Podjęte próby:

  • Próbowałem dodać, @objcale to nie pomaga

Pytanie:

  • Jak mam to rozwiązać?
  • Czy jest w pobliżu praca ?

Kod:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

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


class A : UIViewController, P1 {

}
user1046037
źródło
Czy masz najnowszą wersję Xcode? Nie dostaję żadnych błędów, jeśli @objc
usunę
Używam Xcode 8 (najnowsza wersja publiczna). Nie ma błędu, ale pojawi się ostrzeżenie
user1046037

Odpowiedzi:

184

Myślę, że mogę odpowiedzieć na twoje pytanie, ale nie spodoba ci się to.

TL; DR: @objc funkcje mogą obecnie nie znajdować się w rozszerzeniach protokołu. Zamiast tego można utworzyć klasę bazową, chociaż nie jest to idealne rozwiązanie.

Rozszerzenia protokołów i Objective-C

Po pierwsze, to pytanie / odpowiedź ( Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c ) wydaje się sugerować, że ze względu na sposób, w jaki rozszerzenia protokołów są wysyłane pod maską, metody zadeklarowane w rozszerzeniach protokołów nie są widoczne dla objc_msgSend()funkcji, oraz dlatego nie są widoczne dla kodu Objective-C. Ponieważ metoda, którą próbujesz zdefiniować w swoim rozszerzeniu, musi być widoczna dla Objective-C (więc UIKitmożesz jej użyć), wrzeszczy na ciebie, że nie uwzględniasz @objc, ale kiedy to zrobisz, wrzeszczy na ciebie, ponieważ @objcnie jest dozwolone w rozszerzenia protokołów. Dzieje się tak prawdopodobnie dlatego, że rozszerzenia protokołów nie są obecnie widoczne dla Objective-C.

Widzimy również, że komunikat o błędzie po dodaniu @objcstanów „@objc może być używany tylko z elementami członkowskimi klas, protokołami @objc i konkretnymi rozszerzeniami klas”. To nie jest klasa; rozszerzenie protokołu @objc nie jest tym samym, co znajdowanie się w samej definicji protokołu (tj. w wymaganiach), a słowo „konkretny” sugerowałoby, że rozszerzenie protokołu nie liczy się jako konkretne rozszerzenie klasy.

Obejście problemu

Niestety, to prawie całkowicie uniemożliwia używanie rozszerzeń protokołów, gdy domyślne implementacje muszą być widoczne dla frameworków Objective-C. Na początku pomyślałem, że być może @objcnie jest to dozwolone w twoim rozszerzeniu protokołu, ponieważ Swift Compiler nie mógł zagwarantować, że zgodne typy będą klasami (nawet jeśli specjalnie określono UIViewController). Więc postawiłem classwymóg P1. To nie zadziałało.

Być może jedynym obejściem jest po prostu użycie tutaj klasy bazowej zamiast protokołu, ale oczywiście nie jest to całkowicie idealne, ponieważ klasa może mieć tylko jedną klasę bazową, ale być zgodna z wieloma protokołami.

Jeśli zdecydujesz się pójść tą drogą, weź pod uwagę to pytanie ( metoda protokołu opcjonalnego Swift 3 ObjC nie wywołana w podklasie ). Wydaje się, że innym aktualnym problemem w Swift 3 jest to, że podklasy nie dziedziczą automatycznie implementacji wymagań protokołu opcjonalnego swojej nadklasy. Odpowiedź na te pytania wymaga specjalnego dostosowania, @objcaby to obejść.

Zgłaszanie problemu

Myślę, że jest to już omawiane wśród osób pracujących nad projektami Swift open source, ale możesz być pewien, że są świadomi, używając narzędzia Apple Bug Reporter , który prawdopodobnie trafiłby do Swift Core Team lub do zgłaszającego błędy Swifta . Jednak w obu przypadkach błąd może być zbyt szeroki lub już znany. Zespół Swift może również rozważyć, czego szukasz jako nowej funkcji językowej, w takim przypadku powinieneś najpierw sprawdzić listy mailingowe .

Aktualizacja

W grudniu 2016 r. Ten problem został zgłoszony społeczności Swift. Sprawa jest nadal oznaczona jako otwarta ze średnim priorytetem, ale dodano następujący komentarz:

To jest zamierzone. Nie ma możliwości dodania implementacji metody do każdego adoptera, ponieważ rozszerzenie mogłoby zostać dodane po uzyskaniu zgodności z protokołem. Przypuszczam jednak, że moglibyśmy na to zezwolić, gdyby rozszerzenie znajdowało się w tym samym module co protokół.

Ponieważ Twój protokół znajduje się w tym samym module co rozszerzenie, możesz to zrobić w przyszłej wersji Swift.

Zaktualizuj 2

W lutym 2017 r. Jeden z członków Swift Core Team oficjalnie zamknął ten problem jako „Nie zrobię” z następującym komunikatem:

Jest to zamierzone: rozszerzenia protokołów nie mogą wprowadzać punktów wejścia @objc z powodu ograniczeń środowiska wykonawczego Objective-C. Jeśli chcesz dodać punkty wejścia @objc do NSObject, rozszerz NSObject.

Przedłużenie NSObjectlub nawet UIViewControllernie przyniesie dokładnie tego, czego chcesz, ale niestety nie wygląda na to, że stanie się to możliwe.

W (bardzo) długoterminowej przyszłości możemy @objccałkowicie wyeliminować poleganie na metodach, ale ten czas prawdopodobnie nie nadejdzie w najbliższym czasie, ponieważ frameworki Cocoa nie są obecnie pisane w języku Swift (i nie mogą być, dopóki nie będą miały stabilnego ABI) .

Zaktualizuj 3

Od jesieni 2019 roku staje się to mniejszym problemem, ponieważ coraz więcej frameworków Apple jest pisanych w języku Swift. Na przykład, jeśli użyjesz SwiftUIzamiast UIKit, całkowicie ominiesz problem, ponieważ @objcnigdy nie będzie to konieczne w odniesieniu do SwiftUImetody.

Frameworki Apple napisane w Swift obejmują:

  • SwiftUI
  • RealityKit
  • Połączyć
  • CryptoKit

Można by oczekiwać, że ten wzorzec będzie się utrzymywał w czasie, teraz, gdy Swift jest oficjalnie ABI i stabilny moduł od wersji Swift 5.0 i 5.1.

Matthew Seaman
źródło
1
@ user1046037 Ja również to robię, ponieważ widzę, że wielokrotnie napotykam ten problem w przyszłości.
Matthew Seaman
2
Masz rację, Twoja odpowiedź jest nadal aktualna, mimo że na razie Swift 4nie ma innej alternatywy.
user1046037
1
Miałem dokładnie ten sam kod, który pracował dla mnie przez jakiś czas, ale włamał się w późniejszej wersji Xcode. To dość irytujące. W protokołach Objective-C jest tak wiele opcjonalnych metod.
Departamento B
0

Właśnie natknąłem się na to po włączeniu „stabilności modułu” (włączenie „Buduj biblioteki do dystrybucji”) w szybkim środowisku, którego używam.

To, co miałem, było takie:

class AwesomeClass: LessAwesomeClass {
...
}

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

Funkcja w rozszerzeniu miała następujące błędy:

  • Metoda instancji „@objc” jako rozszerzenie podklasy „LessAwesomeClass” wymaga systemu iOS 13.0.0

  • Non - metoda „@ objc” „niceDelegateFunc” nie spełnia wymagań protokołu „@objc” „GreatDelegate”

Przeniesienie funkcji do klasy zamiast do rozszerzenia rozwiązało problem.

CMash
źródło
0

Oto inne obejście. Również napotkałem ten problem i nie mogę jeszcze przełączyć się z UIKit na SwiftUI. Przeniesienie domyślnych implementacji do wspólnej klasy bazowej również nie było dla mnie opcją. Moje domyślne implementacje były dość obszerne, więc naprawdę nie chciałem, aby cały ten kod był powielany. Obejściem, które ostatecznie zastosowałem, było użycie funkcji opakowujących w protokole, a następnie po prostu wywołanie tych funkcji z każdej klasy. Niezbyt ładne, ale w zależności od sytuacji może być lepsze niż alternatywa. Twój kod wyglądałby wtedy mniej więcej tak:

@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)
    }
}
odnowić
źródło