web-dev-qa-db-fra.com

La méthode d'extension de protocole Swift est appelée à la place de la méthode implémentée dans la sous-classe

J'ai rencontré un problème qui est expliqué dans le code ci-dessous (Swift 3.1):

protocol MyProtocol {
    func methodA()
    func methodB()
}

extension MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocol {

}

class SubClass: BaseClass {
    func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocol {
    func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// Default methodA
// JustClass methodA

Je pense donc que "SubClass methodA" text devrait être imprimé après l'appel object1.methodB(). Mais pour une raison quelconque, l'implémentation par défaut de methodA() à partir d'une extension de protocole est appelée. Cependant, object2.methodB()call fonctionne comme prévu.

Est-ce un autre bogue de Swift dans l'envoi de méthode de protocole ou est-ce que je manque quelque chose et que le code fonctionne correctement?

14
Igor Kulagin

Voici comment les protocoles envoient actuellement des méthodes.

Une table témoin de protocole (voir this conversation WWDC pour plus d'informations) est utilisée afin de répartir de manière dynamique les implémentations des exigences de protocole lors de l'appel d'une instance de type protocole. En réalité, il s’agit simplement d’une liste des implémentations de fonctions à appeler pour chaque exigence du protocole pour un type conforme donné.

Chaque type qui déclare sa conformité à un protocole obtient sa propre table témoin de protocole. Vous noterez que j'ai dit "déclare sa conformité", et pas simplement "se conforme à". BaseClassobtient sa propre table témoin de protocole pour la conformité à MyProtocolname__. Cependant, SubClassn'obtient pas sa propre table pour la conformité à MyProtocol- au lieu de cela, il repose simplement sur BaseClassname __ '. Si vous avez déplacé le
: MyProtocol jusqu'à la définition de SubClassname__, il devrait avoir son propre PWT.

Il ne nous reste donc plus qu'à nous demander à quoi ressemble le PWT pour BaseClassname__. Eh bien, il ne fournit pas d'implémentation pour les exigences de protocole methodA() ou methodB() - il repose donc sur les implémentations de l'extension de protocole. Cela signifie que le PWT pour BaseClassconforme à MyProtocolcontient uniquement des correspondances avec les méthodes d'extension.

Ainsi, lorsque la méthode extension methodB() est appelée et effectue l'appel vers methodA(), elle distribue de manière dynamique cet appel via le PWT (comme elle est appelée sur une instance de type protocole, à savoir selfname__). Ainsi, lorsque cela se produit avec une instance de SubClassname__, nous parcourons le PWT de BaseClassname __. Nous finissons donc par appeler l'implémentation d'extension de methodA(), indépendamment du fait que SubClassen fournit une implémentation.

Considérons maintenant le PWT de JustClassname__. Il fournit une implémentation de methodA(). Par conséquent, son PWT pour la conformité à MyProtocola that implémentation comme mappage pour methodA(), ainsi que l'implémentation d'extension pour methodB(). Ainsi, lorsque methodA() est distribué dynamiquement via son PWT, nous nous retrouvons dans la mise en œuvre de its.

Comme je le disais dans cette Q & A , ce comportement des sous-classes ne recevant pas leurs propres PWT pour les protocoles auxquels leur superclasse se conforme est en effet quelque peu surprenant, et a été classé comme un bogue . Comme l’a expliqué Jordan Rose, membre de l’équipe de Swift, dans les commentaires du rapport de bogue, le raisonnement est 

[...] La sous-classe n'a pas à fournir de nouveaux membres pour satisfaire la conformité. Ceci est important car un protocole peut être ajouté à une classe de base dans un module et une sous-classe créée dans un autre module.

Par conséquent, si tel était le comportement, les sous-classes déjà compilées n'auraient plus de PWT provenant des conformités de super-classes ajoutées après coup dans un autre module, ce qui poserait problème.


Comme d'autres l'ont déjà dit, une solution dans ce cas consiste à faire en sorte que BaseClassfournisse sa propre implémentation de methodA(). Cette méthode sera maintenant dans PWT de BaseClassname __, plutôt que dans la méthode d'extension.

Bien sûr, puisque nous traitons avec classes ici, il ne s'agira pas simplement de l'implémentation de BaseClassname __ de la méthode répertoriée - il s'agira plutôt d'un thunk qui sera ensuite distribué de manière dynamique dans la classe. 'vtable (le mécanisme par lequel les classes réalisent un polymorphisme). Par conséquent, pour une instance SubClassname__, nous allons appeler son remplacement de methodA().

26
Hamish

Dans votre code,

let object1 = SubClass()
object1.methodB()

Vous avez appelé methodB à partir d'une instance de SubClass, mais SubClass n'a aucune méthode nommée methodB. Cependant, sa super classe, BaseClass, est conforme à MyProtocol, qui a une methodB methodB.

Ainsi, il invoquera la methodB à partir de MyProtocal. Par conséquent, il va exécuter la methodA dans extesion MyProtocol.

Pour atteindre vos objectifs, vous devez implémenter methodA dans BaseClass et le remplacer dans SubClass, comme le code suivant.

class BaseClass: MyProtocol {
    func methodA() {
        print("BaseClass methodA")
    }
}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}

Maintenant, la sortie deviendrait

//Output
//SubClass methodA
//JustClass methodA

Bien que la méthode puisse atteindre ce que vous attendez, mais je ne suis pas sûr que ce type de structure de code soit recommandé. 

0
mrfour

Eh bien, je suppose que la méthode de sous-classe A n’est pas polymorphe car vous ne pouvez pas y mettre le mot-clé override, car la classe ne sait pas que la méthode est implémentée dans une extension du protocole et ne vous permet donc pas de la remplacer. La méthode d'extension utilise probablement votre implémentation au moment de l'exécution, un peu comme 2 méthodes de catégories exactes s'affrontent avec un comportement indéfini dans Objective C. Vous pouvez corriger ce problème en ajoutant une autre couche à votre modèle et en implémentant les méthodes dans une classe. extension de protocole, obtenant ainsi un comportement polymorphique. L'inconvénient est que vous ne pouvez pas laisser les méthodes non implémentées dans cette couche, car il n'y a pas de support natif pour les classes abstraites (c'est vraiment ce que vous essayez de faire avec les extensions de protocole).

protocol MyProtocol {
    func methodA()
    func methodB()
}

class MyProtocolClass: MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocolClass {

}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocolClass {
    override func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// SubClass methodA
// JustClass methodA

Répondez également ici: Swift Protocol Extensions écrasant

0
Fernando Mazzon