Non - La méthode '@ objc' ne satisfait pas l'exigence facultative du protocole '@objc'

104

Aperçu:

  • J'ai un protocole P1 qui fournit une implémentation par défaut de l'une des fonctions optionnelles d'Objective-C.
  • Lorsque je fournis une implémentation par défaut de la fonction optionnelle, il y a un avertissement

Avertissement du compilateur:

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

Version:

  • Rapide: 3
  • Xcode: 8 (version publique)

Tentatives effectuées:

  • J'ai essayé d'ajouter @objcmais n'aide pas

Question:

  • Comment résoudre ce problème?
  • Y at-il un travail autour ?

Code:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

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


class A : UIViewController, P1 {

}
utilisateur1046037
la source
Avez-vous la dernière version de Xcode? Je n'obtiens jamais d'erreur si je supprime@objc
Qbyte
J'utilise Xcode 8 (dernière version publique). Il n'y a pas d'erreur, mais il y aura un avertissement
user1046037

Réponses:

184

Bien que je pense pouvoir répondre à votre question, ce n'est pas une réponse qui vous plaira.

TL; DR: @objc fonctions peuvent ne pas être actuellement dans des extensions de protocole. Vous pouvez créer une classe de base à la place, bien que ce ne soit pas une solution idéale.

Extensions de protocole et Objective-C

Tout d'abord, cette question / réponse ( méthode Can Swift définie sur les extensions sur les protocoles accessibles dans Objective-c ) semble suggérer qu'en raison de la façon dont les extensions de protocole sont distribuées sous le capot, les méthodes déclarées dans les extensions de protocole ne sont pas visibles pour la objc_msgSend()fonction, et ne sont donc pas visibles pour le code Objective-C. Étant donné que la méthode que vous essayez de définir dans votre extension doit être visible par Objective-C (vous UIKitpouvez donc l' utiliser), elle vous crie de ne pas l'inclure @objc, mais une fois que vous l'incluez, elle vous crie dessus car elle @objcn'est pas autorisée dans extensions de protocole. Ceci est probablement dû au fait que les extensions de protocole ne peuvent actuellement pas être visibles par Objective-C.

Nous pouvons également voir que le message d'erreur une fois que nous ajoutons les @objcétats "@objc ne peut être utilisé qu'avec des membres de classes, des protocoles @objc et des extensions concrètes de classes." Ce n'est pas une classe; une extension d'un protocole @objc n'est pas la même chose que d'être dans la définition du protocole elle-même (c'est-à-dire dans les exigences), et le mot «concret» suggérerait qu'une extension de protocole ne compte pas comme une extension de classe concrète.

solution de contournement

Malheureusement, cela vous empêche à peu près complètement d'utiliser des extensions de protocole lorsque les implémentations par défaut doivent être visibles pour les frameworks Objective-C. Au début, je pensais que ce @objcn'était peut-être pas autorisé dans votre extension de protocole car le compilateur Swift ne pouvait pas garantir que les types conformes seraient des classes (même si vous avez spécifiquement spécifié UIViewController). J'ai donc mis une classexigence P1. Cela n'a pas fonctionné.

La seule solution de contournement est peut-être d'utiliser simplement une classe de base au lieu d'un protocole ici, mais ce n'est évidemment pas complètement idéal car une classe peut n'avoir qu'une seule classe de base mais se conformer à plusieurs protocoles.

Si vous choisissez cette voie, veuillez prendre en compte cette question ( Méthode de protocole facultative Swift 3 ObjC non appelée dans la sous-classe ). Il semble qu'un autre problème actuel dans Swift 3 est que les sous-classes n'héritent pas automatiquement des implémentations d'exigences de protocole facultatives de leur superclasse. La réponse à ces questions utilise une adaptation spéciale de @objcpour la contourner.

Signaler le problème

Je pense que cela est déjà discuté parmi ceux qui travaillent sur les projets open source Swift, mais vous pouvez être sûr qu'ils sont au courant soit en utilisant le Bug Reporter d'Apple , qui finirait probablement par arriver à l'équipe Swift Core, ou en utilisant le bug Reporter de Swift . Cependant, l'un ou l'autre de ces problèmes peut trouver votre bogue trop large ou déjà connu. L'équipe Swift peut également considérer ce que vous recherchez comme une nouvelle fonctionnalité linguistique, auquel cas vous devriez d'abord consulter les listes de diffusion .

Mettre à jour

En décembre 2016, ce problème a été signalé à la communauté Swift. Le problème est toujours marqué comme ouvert avec une priorité moyenne, mais le commentaire suivant a été ajouté:

C'est prévu. Il n'y a aucun moyen d'ajouter l'implémentation de la méthode à chaque adoptant, puisque l'extension pourrait être ajoutée après la conformité au protocole. Je suppose que nous pourrions l'autoriser si l'extension est dans le même module que le protocole, cependant.

Étant donné que votre protocole se trouve dans le même module que votre extension, vous pourrez peut-être le faire dans une future version de Swift.

Mise à jour 2

En février 2017, ce numéro a été officiellement clos en tant que "Won't Do" par l'un des membres de l'équipe Swift Core avec le message suivant:

Ceci est intentionnel: les extensions de protocole ne peuvent pas introduire de points d'entrée @objc en raison des limitations du runtime Objective-C. Si vous souhaitez ajouter des points d'entrée @objc à NSObject, étendez NSObject.

L'extension NSObjectou même UIViewControllern'accomplira pas exactement ce que vous voulez, mais il ne semble malheureusement pas que cela devienne possible.

Dans un avenir (très) long terme, nous pourrons peut-être éliminer @objccomplètement la dépendance aux méthodes, mais ce moment ne viendra probablement pas de si tôt car les frameworks Cocoa ne sont pas actuellement écrits en Swift (et ne peuvent pas l'être tant qu'ils n'ont pas un ABI stable) .

Mise à jour 3

À partir de l'automne 2019, cela devient moins un problème car de plus en plus de frameworks Apple sont écrits en Swift. Par exemple, si vous utilisez à la SwiftUIplace de UIKit, vous évitez complètement le problème car @objccela ne serait jamais nécessaire pour faire référence à une SwiftUIméthode.

Les frameworks Apple écrits en Swift incluent:

  • SwiftUI
  • RealityKit
  • Combiner
  • CryptoKit

On pourrait s'attendre à ce que ce modèle se poursuive dans le temps maintenant que Swift est officiellement ABI et module stable à partir de Swift 5.0 et 5.1, respectivement.

Matthew Seaman
la source
1
@ user1046037 Je fais aussi bien, car je peux me voir rencontrer ce problème à plusieurs reprises dans le développement futur.
Matthew Seaman
2
Vous avez raison, votre réponse est toujours valable malgré Swift 4aucune autre alternative pour le moment.
user1046037
1
J'avais exactement le même code qui fonctionnait pour moi pendant un certain temps, tout en entrant dans une version ultérieure de Xcode. C'est assez exaspérant. Il y a tellement de méthodes optionnelles dans les protocoles Objective-C.
Departamento B
0

Je viens de rencontrer cela après avoir activé la `` stabilité du module '' (l'activation de `` Créer des bibliothèques pour la distribution '') dans un cadre rapide que j'utilise.

Ce que j'avais était quelque chose comme ça:

class AwesomeClass: LessAwesomeClass {
...
}

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

La fonction dans l'extension avait ces erreurs:

  • La méthode d'instance '@objc' dans l'extension de la sous-classe de 'LessAwesomeClass' nécessite iOS 13.0.0

  • Non - La méthode '@ objc' 'niceDelegateFunc' ne satisfait pas l'exigence du protocole '@objc' 'GreatDelegate'

Le déplacement des fonctions dans la classe plutôt que dans une extension a résolu le problème.

CMash
la source
0

Voici une autre solution de contournement. J'ai également rencontré ce problème et je ne peux pas encore passer d'UIKit à SwiftUI. Déplacer les implémentations par défaut dans une classe de base commune n'était pas non plus une option pour moi. Mes implémentations par défaut étaient assez étendues, donc je ne voulais vraiment pas que tout ce code soit dupliqué. La solution de contournement que j'ai fini par utiliser était d'utiliser des fonctions wrapper dans le protocole, puis d'appeler simplement ces fonctions à partir de chaque classe. Pas joli, mais peut-être mieux que l'alternative, selon la situation. Votre code ressemblerait alors à ceci:

@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
la source