159 votes

Surcharge des méthodes dans les extensions Swift

J'ai tendance à ne mettre que le nécessaire (propriétés stockées, initialisateurs) dans les définitions de mes classes et à déplacer tout le reste dans leurs propres classes. extension comme une sorte de extension par bloc logique que je regrouperais avec // MARK: également.

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

Prenez par exemple cette hiérarchie de classe :

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()

Le résultat est A B C . Cela n'a pas beaucoup de sens pour moi. J'ai lu que les extensions de protocole étaient distribuées de manière statique, mais ce n'est pas un protocole. Il s'agit d'une classe ordinaire, et je m'attends à ce que les appels de méthode soient distribués dynamiquement au moment de l'exécution. Il est clair que l'appel sur C devraient au moins être distribués dynamiquement et produire C ?

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

Déplacer les deux surcharges dans leur déclaration de classe produit A A A comme prévu, ne se déplaçant que B Les produits de l'entreprise A B B en mouvement seulement A Les produits de l'entreprise C B C dont la dernière n'a absolument aucun sens pour moi : pas même celle qui est typée statiquement en A produit le A -plus de sortie !

Ajout de la dynamic Le mot-clé dans la définition ou une surcharge semble me donner le comportement désiré "à partir de ce point dans la hiérarchie de la classe"...

Changeons notre exemple pour quelque chose d'un peu moins construit, ce qui m'a fait poster 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 obtenons maintenant A B A . Ici, je ne peux pas rendre dynamique le layoutSubviews de UIView par quelque moyen que ce soit.

En déplaçant les deux surcharges dans leur déclaration de classe, nous obtenons A A A encore une fois, seulement les A ou seulement les B nous donne toujours A B A . dynamic résout à nouveau mes problèmes.

En théorie, je pourrais ajouter dynamic à tous override mais j'ai l'impression que je fais quelque chose de mal ici.

Est-ce vraiment mal d'utiliser extension pour regrouper le code comme je le fais ?

0 votes

Utiliser les extensions de cette manière est une convention pour Swift. Même Apple le fait dans sa bibliothèque standard.

0 votes

1 votes

@AMomchilov Le document que vous avez lié parle de protocoles, ai-je manqué quelque chose ?

275voto

tymac Points 7397

Les extensions ne peuvent/doivent pas avoir priorité.

Il n'est pas possible de remplacer des fonctionnalités (comme les propriétés ou les méthodes) dans les 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 Swift

Le compilateur vous autorise à passer outre dans l'extension pour des raisons de compatibilité avec l'Objective-C. B

Cela m'a rappelé l'article d'Isaac Asimov " Les trois lois de la robotique "

Extensions ( sucre syntaxique ) définissent des méthodes indépendantes qui reçoivent leurs propres arguments. La fonction qui est appelée, à savoir layoutSubviews dépend du contexte que le compilateur connaît au moment où le code est compilé. UIView hérite de UIResponder qui hérite de NSObject. donc la dérogation dans l'extension est permise mais ne devrait pas être .

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

Notes de la directive

Vous pouvez seulement 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 l'Objective-C.

Nous pouvons donc examiner pourquoi il vous permet de compiler en utilisant layoutSubviews .

Toutes les applications Swift s'exécutent à l'intérieur du moteur d'exécution Objective-C, sauf si vous utilisez des frameworks purement Swift qui permettent un moteur d'exécution Swift uniquement.

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

En ce qui concerne le dynamic modificateur

De la Bibliothèque des développeurs Apple (archive.org)

Vous pouvez utiliser le dynamic pour exiger que l'accès aux membres soit distribué dynamiquement par le runtime Objective-C.

Lorsque les API Swift sont importées par le moteur d'exécution Objective-C, il n'existe aucune garantie de répartition dynamique des propriétés, méthodes, sous-scripts ou initialisateurs. Le compilateur Swift peut encore dévirtualiser ou mettre en ligne les accès aux membres pour optimiser les performances de votre code, en contournant le runtime Objective-C.

Alors dynamic peut être appliqué à votre layoutSubviews -> UIView Class puisqu'il est représenté par l'Objective-C et que l'accès à ce membre est toujours utilisé en utilisant le runtime de l'Objective-C.

C'est pourquoi le compilateur vous permet d'utiliser override y dynamic .

9 votes

L'extension ne peut pas surcharger uniquement les méthodes définies dans la classe. Elle peut remplacer les méthodes définies dans la classe parente.

0 votes

-Swift3-Bien, c'est bizarre, parce que vous pouvez aussi surcharger (et par surcharger ici je veux dire quelque chose comme Swizzling) les méthodes des frameworks que vous incluez. même si ces frameworks sont écrits en swift.... pur peut-être que les frameworks sont aussi liés à objc et c'est pourquoi

0 votes

@tymac Je ne comprends pas. Si le Runtime Objective-C Si vous avez besoin de quelque chose pour assurer la compatibilité avec l'Objective-C, pourquoi ? Compilateur Swift permet-il toujours le surclassement dans les extensions ? Comment le fait de marquer les surcharges dans les extensions Swift comme des erreurs de syntaxe peut nuire au runtime Objective-C ?

20voto

Wain Points 65967

L'un des objectifs de Swift est le dispatching statique, ou plutôt la réduction du dispatching dynamique. Obj-C est cependant un langage très dynamique. La situation que vous observez est due au lien entre les 2 langages et à la façon dont ils fonctionnent ensemble. Il ne devrait pas vraiment compiler.

L'un des principaux points concernant les extensions est qu'elles servent à étendre, et non à remplacer / surcharger. Le nom et la documentation montrent clairement que telle est l'intention. En effet, si vous enlevez le lien vers Obj-C de votre code (enlevez NSObject comme superclasse), il ne compilera pas.

Ainsi, le compilateur essaie de décider ce qu'il peut distribuer statiquement et ce qu'il doit distribuer dynamiquement, et il tombe dans un vide à cause du lien Obj-C dans votre code. La raison dynamic fonctionne", c'est parce qu'il force la liaison Obj-C sur tout, de sorte que tout est toujours dynamique.

Ainsi, il n'y a rien de mal à utiliser les extensions pour le regroupement, c'est très bien, mais il n'y a rien de mal à modifier les extensions. Toutes les surcharges doivent être dans la classe principale elle-même, et faire appel aux points d'extension.

1 votes

Cela s'applique également aux variables ? Par exemple, si vous voulez remplacer supportedInterfaceOrientations sur UINavigationController (dans le but de montrer différentes vues dans différentes orientations), vous devez utiliser une classe personnalisée et non une extension ? De nombreuses réponses suggèrent d'utiliser une extension pour remplacer supportedInterfaceOrientations mais j'aimerais avoir des éclaircissements. Merci !

0 votes

@Crashalot Je pense que vous voulez parler de "propriétés". En Objective-C, les propriétés sont implémentées en coulisses comme des méthodes getter/setter, donc la même logique s'applique.

14voto

Alain T. Points 1649

Il existe un moyen d'obtenir une séparation nette entre la signature de la classe et son implémentation (dans les extensions) tout en conservant la possibilité d'avoir des surcharges dans les sous-classes. L'astuce consiste à utiliser des variables à la place des fonctions

Si vous veillez à définir chaque sous-classe dans un fichier source Swift distinct, vous pouvez utiliser des variables calculées pour les surcharges tout en conservant l'implémentation correspondante proprement organisée dans les extensions. Cela permet de contourner les "règles" de Swift et d'organiser proprement l'API/signature de votre classe 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))" 
   }
}

L'extension de chaque classe peut utiliser les mêmes noms de méthodes pour l'implémentation car elles sont privées et non visibles l'une pour l'autre (tant qu'elles sont dans des fichiers séparés).

Comme vous pouvez le voir, l'héritage (en utilisant le nom de la variable) fonctionne correctement en utilisant 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"

4 votes

Je suppose que cela fonctionnerait pour mes propres méthodes, mais pas lorsque je remplace des méthodes du System Framework dans mes classes.

1 votes

Cela m'a conduit sur la bonne voie pour une extension du protocole conditionnel de l'enveloppe de propriété. Merci !

0 votes

Pouvez-vous développer ce point ? "Cela m'a conduit sur la bonne voie pour une extension du protocole conditionnel de l'enveloppe de propriété."

3voto

RenniePet Points 2388

Cette réponse ne s'adresse pas au PO, si ce n'est que je me suis senti inspiré pour répondre à sa déclaration, "J'ai tendance à ne mettre que le nécessaire (propriétés stockées, initialisateurs) dans mes définitions de classe et à déplacer 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 distinct à l'aide d'une classe partielle, et laisse votre fichier source principal non encombré afin que vous n'ayez pas cette distraction.

Si vous cherchez "swift partial class", vous trouverez plusieurs liens où les adeptes de Swift affirment que Swift n'a pas besoin de classes partielles parce que vous pouvez utiliser des extensions. Il est intéressant de noter que si vous tapez "swift extension" dans le champ de recherche de Google, la première suggestion de recherche est "swift extension override", et pour le moment, cette question de Stack Overflow est le premier résultat. J'en déduis que les problèmes liés à l'absence de possibilités de remplacement sont le sujet le plus recherché en ce qui concerne les extensions Swift, et cela souligne le fait que les extensions Swift ne peuvent pas remplacer les classes partielles, du moins si vous utilisez des classes dérivées dans votre programmation.

Quoi qu'il en soit, pour faire court, j'ai rencontré ce problème dans une situation où je voulais retirer certaines méthodes de type "boilerplate" ou "baggage" des fichiers sources principaux des classes Swift que mon programme C# vers Swift générait. Après avoir rencontré le problème de l'impossibilité de surcharger ces méthodes après les avoir déplacées vers des extensions, j'ai fini par mettre en œuvre la solution de contournement simple suivante. Les fichiers sources principaux de Swift contiennent toujours quelques petites méthodes stub qui appellent les vraies méthodes dans les fichiers d'extension, et ces méthodes d'extension reçoivent des noms uniques pour éviter le problème d'override.

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 de l'OP, mais j'espère que cette solution simple d'esprit pourra être utile à d'autres personnes qui souhaitent déplacer des méthodes des fichiers sources principaux vers des fichiers d'extension et qui rencontrent le problème du no-override.

3voto

muyexi Points 1

Utilisez le protocole POP (Protocol-Oriented Programming) pour remplacer les fonctions des extensions.

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 votes

Cela suppose que le programmeur contrôle la définition originale de AClass de telle sorte qu'il puisse s'appuyer sur AProtocol. Dans la situation où l'on veut remplacer la fonctionnalité de AClass, ce n'est généralement pas le cas (c'est-à-dire que AClass serait probablement une classe de la bibliothèque standard fournie par Apple).

0 votes

Notez que vous pouvez (dans certains cas) appliquer le protocole dans une extension ou une sous-classe si vous ne voulez ou ne pouvez pas modifier la définition originale de la classe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X