Die Nicht-Methode '@ objc' erfüllt nicht die optionale Anforderung des Protokolls '@objc'

103

Überblick:

  • Ich habe ein Protokoll P1, das eine Standardimplementierung einer der optionalen Objective-C-Funktionen bietet.
  • Wenn ich eine Standardimplementierung der optionalen Funktion bereitstelle, wird eine Warnung angezeigt

Compiler Warnung:

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

Ausführung:

  • Swift: 3
  • Xcode: 8 (Veröffentlichung)

Versuche gemacht:

  • Versucht hinzuzufügen @objc, hilft aber nicht

Frage:

  • Wie löse ich das?
  • Gibt es eine Lösung?

Code:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

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


class A : UIViewController, P1 {

}
user1046037
quelle
Haben Sie die neueste Version von Xcode? Ich bekomme nie Fehler, wenn ich entferne@objc
Qbyte
Ich verwende Xcode 8 (neueste öffentliche Version). Es gibt keinen Fehler, aber es wird eine Warnung
angezeigt

Antworten:

182

Ich denke, ich kann Ihre Frage beantworten, aber es ist keine Antwort, die Ihnen gefallen wird.

TL; DR: @objc Funktionen befinden sich derzeit möglicherweise nicht in Protokollerweiterungen. Sie können stattdessen eine Basisklasse erstellen, obwohl dies keine ideale Lösung ist.

Protokollerweiterungen und Ziel-C

Erstens scheint diese Frage / Antwort ( Kann Swift-Methode für Erweiterungen von Protokollen definiert werden, auf die in Objective-c zugegriffen wird ) darauf hinzudeuten, dass aufgrund der Art und Weise, wie Protokollerweiterungen unter der Haube versendet werden, in Protokollerweiterungen deklarierte Methoden für die objc_msgSend()Funktion und nicht sichtbar sind sind daher für Objective-C-Code nicht sichtbar. Da die Methode, die Sie in Ihrer Erweiterung definieren möchten, für Objective-C sichtbar sein muss (damit UIKitsie verwendet werden kann), schreit @objcsie Sie an, weil Sie sie nicht aufgenommen haben. Sobald Sie sie aufgenommen haben, schreit sie Sie an, weil sie @objcnicht zulässig ist Protokollerweiterungen. Dies liegt wahrscheinlich daran, dass Protokollerweiterungen derzeit für Objective-C nicht sichtbar sind.

Wir können auch sehen, dass die Fehlermeldung nach dem Hinzufügen den @objcStatus "@objc kann nur mit Mitgliedern von Klassen, @objc-Protokollen und konkreten Erweiterungen von Klassen verwendet werden." Dies ist keine Klasse; Eine Erweiterung eines @ objc-Protokolls ist nicht dasselbe wie in der Protokolldefinition selbst (dh in den Anforderungen), und das Wort "konkret" würde darauf hinweisen, dass eine Protokollerweiterung nicht als konkrete Klassenerweiterung gilt.

Problemumgehung

Leider verhindert dies so ziemlich vollständig, dass Sie Protokollerweiterungen verwenden können, wenn die Standardimplementierungen für Objective-C-Frameworks sichtbar sein müssen. Zuerst dachte ich, dass @objcIhre Protokollerweiterung möglicherweise nicht zulässig ist, da der Swift Compiler nicht garantieren kann, dass konforme Typen Klassen sind (obwohl Sie dies speziell angegeben haben UIViewController). Also habe ich eine classAnforderung gestellt P1. Das hat nicht funktioniert.

Vielleicht besteht die einzige Problemumgehung darin, hier einfach eine Basisklasse anstelle eines Protokolls zu verwenden, aber dies ist offensichtlich nicht ganz ideal, da eine Klasse möglicherweise nur eine einzige Basisklasse hat, aber mehreren Protokollen entspricht.

Wenn Sie sich für diesen Weg entscheiden, berücksichtigen Sie bitte diese Frage ( optionale Swift 3 ObjC-Protokollmethode, die in der Unterklasse nicht aufgerufen wird ). Es scheint, dass ein weiteres aktuelles Problem in Swift 3 darin besteht, dass Unterklassen die optionalen Protokollanforderungsimplementierungen ihrer Oberklasse nicht automatisch erben. Die Antwort auf diese Fragen verwendet eine spezielle Anpassung von @objc, um sie zu umgehen.

Problem melden

Ich denke, dies wird bereits unter denjenigen diskutiert, die an den Swift-Open-Source-Projekten arbeiten, aber Sie können sicher sein, dass sie sich dessen bewusst sind, indem Sie entweder Apples Bug Reporter verwenden , der wahrscheinlich irgendwann den Weg zum Swift Core Team finden würde, oder Swifts Bug Reporter . In beiden Fällen ist Ihr Fehler möglicherweise zu weit gefasst oder bereits bekannt. Das Swift-Team kann auch in Betracht ziehen, wonach Sie suchen, um eine neue Sprachfunktion zu erhalten. In diesem Fall sollten Sie zuerst die Mailinglisten überprüfen .

Aktualisieren

Im Dezember 2016 wurde dieses Problem der Swift-Community gemeldet . Das Problem wird weiterhin als offen mit mittlerer Priorität markiert, aber der folgende Kommentar wurde hinzugefügt:

Dies ist beabsichtigt. Es gibt keine Möglichkeit, die Implementierung der Methode jedem Anwender hinzuzufügen, da die Erweiterung nach der Konformität mit dem Protokoll hinzugefügt werden könnte. Ich nehme an, wir könnten es zulassen, wenn sich die Erweiterung im selben Modul wie das Protokoll befindet.

Da sich Ihr Protokoll jedoch im selben Modul wie Ihre Erweiterung befindet, können Sie dies möglicherweise in einer zukünftigen Version von Swift tun.

Update 2

Im Februar 2017 wurde diese Ausgabe von einem der Mitglieder des Swift Core Teams mit der folgenden Meldung offiziell als "Won't Do" geschlossen :

Dies ist beabsichtigt: Protokollerweiterungen können aufgrund von Einschränkungen der Objective-C-Laufzeit keine @objc-Einstiegspunkte einführen. Wenn Sie NSObject @objc-Einstiegspunkte hinzufügen möchten, erweitern Sie NSObject.

Erweitern NSObjectoder sogar UIViewControllernicht genau das erreichen, was Sie wollen, aber es sieht leider nicht so aus, als würde es möglich werden.

In der (sehr) langfristigen Zukunft können wir möglicherweise die Abhängigkeit von @objcMethoden vollständig beseitigen , aber diese Zeit wird wahrscheinlich nicht so bald kommen, da Cocoa-Frameworks derzeit nicht in Swift geschrieben sind (und erst dann verfügbar sein können, wenn es einen stabilen ABI hat). .

Update 3

Ab Herbst 2019 wird dies weniger problematisch, da immer mehr Apple-Frameworks in Swift geschrieben werden. Wenn Sie beispielsweise SwiftUIanstelle von verwenden UIKit, umgehen Sie das Problem vollständig, da @objcdies bei der Bezugnahme auf eine SwiftUIMethode niemals erforderlich wäre .

In Swift geschriebene Apple-Frameworks umfassen:

  • SwiftUI
  • RealityKit
  • Kombinieren
  • CryptoKit

Man würde erwarten, dass sich dieses Muster im Laufe der Zeit fortsetzt, da Swift ab Swift 5.0 bzw. 5.1 offiziell ABI- und modulstabil ist.

Matthew Seaman
quelle
1
@ user1046037 Das mache ich auch, da ich sehe, dass ich in der zukünftigen Entwicklung oft auf dieses Problem stoße.
Matthew Seaman
2
Sie haben Recht, Ihre Antwort gilt trotz Swift 4keiner anderen Alternative.
user1046037
1
Ich hatte eine Zeit lang genau den gleichen Code für mich, der jedoch eine spätere Version von Xcode einbrach. Das ist ziemlich ärgerlich. Es gibt so viele optionale Methoden in Objective-C-Protokollen.
Departamento B
0

Ich bin gerade darauf gestoßen, nachdem ich 'Modulstabilität' (Aktivieren von 'Bibliotheken für die Verteilung erstellen') in einem von mir verwendeten schnellen Framework aktiviert habe.

Was ich hatte, war ungefähr so:

class AwesomeClass: LessAwesomeClass {
...
}

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

Die Funktion in der Erweiterung hatte folgende Fehler:

  • Die Instanzmethode '@objc' in Erweiterung der Unterklasse von 'LessAwesomeClass' erfordert iOS 13.0.0

  • Nicht-'@ objc'-Methode' niceDelegateFunc 'erfüllt nicht die Anforderung des' @objc'-Protokolls 'GreatDelegate'

Durch Verschieben der Funktionen in die Klasse und nicht in eine Erweiterung wurde das Problem behoben.

CMash
quelle
0

Hier ist eine weitere Problemumgehung. Ich bin auch auf dieses Problem gestoßen und kann noch nicht von UIKit zu SwiftUI wechseln. Das Verschieben der Standardimplementierungen in eine gemeinsame Basisklasse war für mich ebenfalls keine Option. Meine Standardimplementierungen waren ziemlich umfangreich, so dass ich wirklich nicht wollte, dass der gesamte Code dupliziert wird. Die Problemumgehung, die ich letztendlich verwendet habe, bestand darin, Wrapper-Funktionen im Protokoll zu verwenden und diese Funktionen dann einfach aus jeder Klasse aufzurufen. Nicht schön, aber je nach Situation vielleicht besser als die Alternative. Ihr Code würde dann ungefähr so ​​aussehen:

@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)
    }
}
rene
quelle