web-dev-qa-db-fra.com

Remplacement de méthodes dans les extensions Swift

J'ai tendance à ne mettre que les nécessités (propriétés stockées, initialiseurs) dans mes définitions de classe et à déplacer tout le reste dans leur propre extension, un peu comme un extension par bloc logique que je grouperais avec // MARK: ainsi que.

Pour une sous-classe UIView par exemple, je me retrouverais avec une extension pour les éléments liés à la mise en page, une autre pour la souscription et la gestion des événements, etc. Dans ces extensions, je dois inévitablement remplacer certaines méthodes UIKit, par exemple. layoutSubviews. Je n'ai jamais remarqué de problèmes avec cette approche - jusqu'à aujourd'hui.

Prenons cette hiérarchie de classe par exemple:

public class C: NSObject {
    public func method() { print("C") }
}

public class B: C {
}
extension B {
    override public func method() { print("B") }
}

public class A: B {
}
extension A {
    override public func method() { print("A") }
}

(A() as A).method()
(A() as B).method()
(A() as C).method()

La sortie est A B C. Cela n'a pas beaucoup de sens pour moi. J'ai lu que les extensions de protocole étaient distribuées statiquement, mais ce n'est pas un protocole. Il s’agit d’une classe standard et j’espère que les appels de méthode seront distribués de manière dynamique au moment de l’exécution. Il est clair que l'appel sur C devrait au moins être envoyé de manière dynamique et produire C?

Si je supprime l'héritage de NSObject et que je fais de C une classe racine, le compilateur se plaint en disant declarations in extensions cannot override yet, ce que j'ai déjà lu. Mais comment avoir NSObject en tant que classe racine change-t-il les choses?

Déplacer les deux substitutions dans leur déclaration de classe produit A A A comme prévu, ne déplacer que B produit A B B, ne déplacer que A produit C B C, le dernier d'entre eux rendant absolument cela n'a aucun sens pour moi: même celui qui est statiquement typé dans A ne produit plus la sortie A-!

L'ajout du mot clé dynamic à la définition ou un remplacement semble me donner le comportement souhaité 'à partir de ce point dans la hiérarchie des classes vers le bas' ...

Changeons notre exemple pour quelque chose d'un peu moins construit, ce qui m'a amené à poser cette question:

public class B: UIView {
}
extension B {
    override public func layoutSubviews() { print("B") }
}

public class A: B {
}
extension A {
    override public func layoutSubviews() { print("A") }
}


(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()

Nous avons maintenant A B A. Ici, je ne peux en aucun cas dynamiser le layoutSubviews d'UIView.

Déplacer les deux substitutions dans leur déclaration de classe nous rapporte A A A encore une fois, seuls les A ou seuls B nous récupèrent toujours A B A. dynamic résout à nouveau mes problèmes.

En théorie, je pourrais ajouter dynamic à tous les overrides que je fais jamais, mais je sens que je fais autre chose de mal ici.

Est-ce vraiment une erreur d'utiliser extensions pour regrouper le code comme je le fais?

114
Christian Schnorr

Les extensions ne peuvent/ne doivent pas remplacer.

Il n'est pas possible de remplacer des fonctionnalités (telles que des propriétés ou des méthodes) dans des extensions, comme indiqué dans le Guide Swift d'Apple.

Les extensions peuvent ajouter de nouvelles fonctionnalités à un type, mais elles ne peuvent pas remplacer les fonctionnalités existantes.

Guide du développeur Apple

Le compilateur vous permet de remplacer l'extension pour la compatibilité avec Objective-C. Mais cela enfreint réellement la directive language.

Cela me faisait penser à Isaac Asimov " Trois lois de la robotique " ????

Les extensions ( sucre syntaxique ) définissent des méthodes indépendantes qui reçoivent leurs propres arguments. La fonction appelée pour layoutSubviews dépend du contexte que le compilateur connaît lorsque le code est compilé. UIView hérite de UIResponder qui hérite de NSObject , de sorte que le remplacement dans l'extension est autorisé mais ne doit pas être .

Il n'y a donc rien de mal à grouper, mais vous devez remplacer dans la classe et non dans l'extension.

Notes de directive

Vous ne pouvez que override une méthode de superclasse, c'est-à-dire load()initialize() dans une extension d'une sous-classe si la méthode est compatible avec Objective-C.

Par conséquent, nous pouvons voir pourquoi cela vous permet de compiler avec layoutSubviews.

Toutes les applications Swift s'exécutent dans le runtime d'Objective-C sauf lorsque vous utilisez des infrastructures purement Swift-only qui permettent un runtime Swift-only.

Comme nous l'avons découvert, le runtime d'Objective-C appelle généralement deux méthodes principales de classe load() et initialize() automatiquement lors de l'initialisation de classes dans les processus de votre application.

Concernant le modificateur dynamic

De la bibliothèque de développement iOS

Vous pouvez utiliser le modificateur dynamic pour exiger que l'accès aux membres soit distribué de manière dynamique via le moteur d'exécution Objective-C.

Lorsque Swift API sont importés par le moteur d'exécution Objective-C, rien ne garantit une répartition dynamique des propriétés, méthodes, sous-scripts ou initialiseurs. Le compilateur Swift peut toujours devirtualiser un accès membre en ligne afin d'optimiser les performances de votre code, en contournant le temps d'exécution Objective-C. ? ???

Ainsi, dynamic peut être appliqué à votre layoutSubviews -> UIView Class puisqu'il est représenté par Objective-C et que l'accès à ce membre est toujours utilisé à l'aide de l'exécution Objective-C.

C'est pourquoi le compilateur vous permettant d'utiliser override et dynamic.

185
tymac

Un des objectifs de Swift est la répartition statique, ou plutôt la réduction de la répartition dynamique. Obj-C est cependant un langage très dynamique. La situation que vous voyez est née du lien entre les deux langues et de la manière dont elles fonctionnent ensemble. Ça ne devrait pas vraiment compiler.

L'un des principaux points concernant les extensions est qu'elles sont destinées à l'extension, pas au remplacement/à la substitution. Le nom et la documentation indiquent clairement que telle est l'intention. En effet, si vous retirez le lien vers Obj-C de votre code (supprimez NSObject comme superclasse), il ne sera pas compilé.

Ainsi, le compilateur essaie de décider ce qu'il peut envoyer statiquement et ce qu'il doit envoyer dynamiquement, et il est en train de passer à travers une lacune en raison du lien Obj-C dans votre code. La raison pour laquelle dynamic 'fonctionne' est que cela force la liaison Obj-C sur tout, donc tout est toujours dynamique.

Donc, utiliser des extensions pour le regroupement n’est pas une erreur, c’est génial, mais il est faux de remplacer les extensions. Tous les remplacements doivent être dans la classe principale elle-même et appeler les points d'extension.

16
Wain

Il existe un moyen de séparer clairement la signature de classe et la mise en œuvre (dans les extensions) tout en maintenant la possibilité de remplacer les sous-classes. L'astuce consiste à utiliser des variables à la place des fonctions

Si vous vous assurez de définir chaque sous-classe dans un fichier source séparé Swift, vous pouvez utiliser des variables calculées pour les substitutions tout en maintenant la mise en oeuvre correspondante organisée en extensions. Cela contournera les "règles" de Swift et rendra l'API/la signature de votre classe parfaitement organisée en un seul endroit:

 // ---------- BaseClass.Swift -------------

 public class BaseClass
 {
     public var method1:(Int) -> String { return doMethod1 }

     public init() {}
 }

 // the extension could also be in a separate file  
 extension BaseClass
 {    
     private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
 }

...

 // ---------- ClassA.Swift ----------

 public class A:BaseClass
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 // this extension can be in a separate file but not in the same
 // file as the BaseClass extension that defines its doMethod1 implementation
 extension A
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "A \(param) added to \(super.method1(param))" 
    }
 }

...

 // ---------- ClassB.Swift ----------
 public class B:A
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 extension B
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "B \(param) added to \(super.method1(param))" 
    }
 }

Les extensions de chaque classe peuvent utiliser les mêmes noms de méthodes pour l'implémentation car elles sont privées et non visibles les unes des autres (tant qu'elles se trouvent dans des fichiers séparés).

Comme vous pouvez le constater, l'héritage (avec le nom de la variable) fonctionne correctement avec super.variablename

 BaseClass().method1(123)         --> "BaseClass 123"
 A().method1(123)                 --> "A 123 added to BaseClass 123"
 B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
 (B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
 (B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"
8
Alain T.

Utilisez POP (Protocol-Oriented Programming) pour remplacer les fonctions des postes.

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()
1
Winter

Cette réponse ne visait pas le PO, mis à part le fait que je me sentais inspirée par sa déclaration: "J'ai tendance à ne mettre que les nécessités (propriétés stockées, initialiseurs) dans les définitions de classe et à transférer tout le reste dans leur propre extension. .. ". Je suis principalement un programmeur C #, et en C #, on peut utiliser des classes partielles à cette fin. Par exemple, Visual Studio place les éléments liés à l'interface utilisateur dans un fichier source séparé à l'aide d'une classe partielle et laisse votre fichier source principal non encombré afin d'éviter toute distraction.

Si vous recherchez "Classe partielle Swift", vous trouverez plusieurs liens dans lesquels Swift adhérents disent que Swift n'a pas besoin de classes partielles car vous pouvez utiliser des extensions. Il est intéressant de noter que si vous tapez "extension rapide" dans le champ de recherche Google, sa première suggestion de recherche est "remplacement prioritaire de Swift" et, pour le moment, cette question de débordement de pile est le premier résultat. Je suppose que cela signifie que les problèmes avec (l’absence de) capacité de substitution sont le sujet le plus recherché en ce qui concerne les extensions Swift, et souligne le fait que les extensions Swift ne peuvent pas remplacer classes partielles, du moins si vous utilisez des classes dérivées dans votre programmation.

Quoi qu'il en soit, pour résumer une longue introduction, je me suis heurté à ce problème dans une situation où je voulais déplacer certaines méthodes standard/baggage des fichiers source principaux pour les classes Swift que mon C # -to- Le programme Swift était en train de générer. Après avoir rencontré le problème de l'absence de substitution autorisée pour ces méthodes après les avoir déplacées vers des extensions, j'ai fini par implémenter la solution de contournement simple suivante. Les fichiers source principaux Swift contiennent toujours quelques méthodes de stub minuscules qui appellent les méthodes réelles dans les fichiers d'extension, et ces méthodes d'extension reçoivent des noms uniques pour éviter le problème de remplacement.

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

.

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

.

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

.

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

.

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

Comme je l'ai dit dans mon introduction, cela ne répond pas vraiment à la question du PO, mais j'espère que cette solution de contournement simpliste pourrait être utile pour les autres personnes souhaitant déplacer des méthodes des fichiers sources principaux vers des fichiers d'extension et se lancer dans la commande no. problème de -verride.

1
RenniePet