web-dev-qa-db-fra.com

Protocole de retour de soi

J'ai un protocole P qui retourne une copie de l'objet:

protocol P {
    func copy() -> Self
}

et une classe C qui implémente P:

class C : P {
    func copy() -> Self {
        return C()
    }
}

Toutefois, si je mets la valeur de retour sous la forme Self, l'erreur suivante apparaît: 

Impossible de convertir une expression de retour de type 'C' en type 'Self'

J'ai aussi essayé de retourner C

class C : P {
    func copy() -> C  {
        return C()
    }
}

Cela a entraîné l'erreur suivante: 

La méthode 'copy ()' de la classe non finale 'C' doit renvoyer Self pour être conforme au protocole 'P'

Rien ne fonctionne sauf dans le cas où je préfixe class C avec final c'est-à-dire do: 

final class C : P {
    func copy() -> C  {
        return C()
    }
}

Cependant, si je veux utiliser la sous-classe C, rien ne fonctionnerait. Y a-t-il un moyen de contourner cela?

61
aeubanks

Le problème est que vous faites une promesse que le compilateur ne peut pas prouver que vous allez tenir.

Vous avez donc créé cette promesse: appeler copy() renverra son propre type, entièrement initialisé.

Mais alors vous avez implémenté copy() de cette façon:

func copy() -> Self {
    return C()
}

Maintenant, je suis une sous-classe qui ne remplace pas copy(). Et je retourne une C, pas une Self entièrement initialisée (que j’ai promise). Donc ce n'est pas bon. Que diriez-vous:

func copy() -> Self {
    return Self()
}

Eh bien, ça ne compilera pas, mais même si c'était le cas, ça ne servirait à rien. La sous-classe peut ne pas avoir de constructeur trivial, donc D() pourrait même ne pas être légal. (Bien que voir ci-dessous.)

OK, bien que diriez-vous de:

func copy() -> C {
    return C()
}

Oui, mais cela ne retourne pas Self. Il retourne C. Vous ne tenez toujours pas votre promesse.

"Mais ObjC peut le faire!" Eh bien, en quelque sorte. Principalement parce que cela n'a pas d'importance si vous tenez votre promesse comme le fait Swift. Si vous ne parvenez pas à implémenter copyWithZone: dans la sous-classe, vous risquez de ne pas initialiser complètement votre objet. Le compilateur ne vous avertira même pas que vous avez fait cela.

"Mais presque tout dans ObjC peut être traduit en Swift, et ObjC a NSCopying." Oui, et voici comment cela est défini:

func copy() -> AnyObject!

Donc, vous pouvez faire la même chose (il n'y a aucune raison pour le! Ici):

protocol Copyable {
  func copy() -> AnyObject
}

Cela dit "je ne promets rien sur ce que vous récupérez." Vous pouvez aussi dire:

protocol Copyable {
  func copy() -> Copyable
}

C'est une promesse que vous pouvez faire.

Mais nous pouvons penser au C++ pendant un petit moment et nous rappeler qu’il ya une promesse que nous pouvons faire. Nous pouvons promettre que nous et toutes nos sous-classes mettrons en œuvre des types d'initialisateurs spécifiques, et Swift le fera respecter (et pourra ainsi prouver que nous disons la vérité):

protocol Copyable {
  init(copy: Self)
}

class C : Copyable {
  required init(copy: C) {
    // Perform your copying here.
  }
}

Et c'est ainsi que vous devriez effectuer des copies.

Nous pouvons aller un peu plus loin, mais il utilise dynamicType, et je ne l’ai pas testé de manière approfondie pour vérifier que c’est toujours ce que nous voulons, mais cela devrait être correct:

protocol Copyable {
  func copy() -> Self
  init(copy: Self)
}

class C : Copyable {
  func copy() -> Self {
    return self.dynamicType(copy: self)
  }

  required init(copy: C) {
    // Perform your copying here.
  }
}

Nous promettons ici qu’il existe un initialiseur qui effectue des copies pour nous, puis nous pouvons au moment de l’exécution déterminer lequel appeler, en nous donnant la syntaxe de méthode que vous recherchiez.

133
Rob Napier

Avec Swift 2, nous pouvons utiliser des extensions de protocole pour cela.

protocol Copyable {
    init(copy:Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(copy: self)
    }
}
23
Tolga Okur

En fait, il existe un truc qui permet de facilement retourner Self lorsque requis par un protocole ( Gist ):

/// Cast the argument to the infered function return type.
func autocast<T>(some: Any) -> T? {
    return some as? T
}

protocol Foo {
    static func foo() -> Self
}

class Vehicle: Foo {
    class func foo() -> Self {
        return autocast(Vehicle())!
    }
}

class Tractor: Vehicle {
    override class func foo() -> Self {
        return autocast(Tractor())!
    }
}

func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

let vehicle = Vehicle.foo()
let tractor = Tractor.foo()

print(typeName(vehicle)) // Vehicle
print(typeName(tractor)) // Tractor
10
werediver

Il existe une autre façon de faire ce que vous voulez, c'est tirer parti du type associé à Swift. Voici un exemple simple:

public protocol Creatable {

    associatedtype ObjectType = Self

    static func create() -> ObjectType
}

class MyClass {

    // Your class stuff here
}

extension MyClass: Creatable {

    // Define the protocol function to return class type
    static func create() -> MyClass {

         // Create an instance of your class however you want
        return MyClass()
    }
}

let obj = MyClass.create()
9
Matt Mendrala

Suivant la suggestion de Rob, cela pourrait être rendu plus générique avec types associés . J'ai un peu modifié l'exemple pour démontrer les avantages de l'approche.

protocol Copyable: NSCopying {
    associatedtype Prototype
    init(copy: Prototype)
    init(deepCopy: Prototype)
}
class C : Copyable {
    typealias Prototype = C // <-- requires adding this line to classes
    required init(copy: Prototype) {
        // Perform your copying here.
    }
    required init(deepCopy: Prototype) {
        // Perform your deep copying here.
    }
    @objc func copyWithZone(zone: NSZone) -> AnyObject {
        return Prototype(copy: self)
    }
}
2
David James

J'ai eu un problème similaire et j'ai trouvé quelque chose qui pourrait être utile, alors je pense que je le partagerais pour référence future, car c'est l'un des premiers endroits que j'ai trouvés lors de la recherche d'une solution.

Comme indiqué ci-dessus, le problème est l’ambiguïté du type de retour pour la fonction copy (). Ceci peut être illustré très clairement en séparant les fonctions copy () -> C et copy () -> P:

Donc, en supposant que vous définissiez le protocole et la classe comme suit:

protocol P
{
   func copy() -> P
}

class C:P  
{        
   func doCopy() -> C { return C() }       
   func copy() -> C   { return doCopy() }
   func copy() -> P   { return doCopy() }       
}

Cela compile et produit les résultats attendus lorsque le type de la valeur de retour est explicite. Chaque fois que le compilateur doit décider quel type de retour devrait être (seul), il trouvera la situation ambiguë et échouera pour toutes les classes concrètes qui implémentent le protocole P.

Par exemple:

var aC:C = C()   // aC is of type C
var aP:P = aC    // aP is of type P (contains an instance of C)

var bC:C         // this to test assignment to a C type variable
var bP:P         //     "       "         "      P     "    "

bC = aC.copy()         // OK copy()->C is used

bP = aC.copy()         // Ambiguous. 
                       // compiler could use either functions
bP = (aC as P).copy()  // but this resolves the ambiguity.

bC = aP.copy()         // Fails, obvious type incompatibility
bP = aP.copy()         // OK copy()->P is used

En conclusion, cela fonctionnerait dans les cas où vous n'utiliseriez pas la fonction copy () de la classe de base ou que vous avez toujours un contexte de type explicite.

J'ai constaté que le même nom de fonction que la classe concrète permettait de manipuler le code partout, alors je me suis retrouvé avec un nom différent pour la fonction copy () du protocole. 

Le résultat final ressemble plus à:

protocol P
{
   func copyAsP() -> P
}

class C:P  
{
   func copy() -> C 
   { 
      // there usually is a lot more code around here... 
      return C() 
   }
   func copyAsP() -> P { return copy() }       
}

Bien sûr, mon contexte et mes fonctions sont complètement différents, mais dans l’esprit de la question, j’ai essayé de rester aussi proche que possible de l’exemple donné.

1
Alain T.

Pour ajouter aux réponses avec la méthode associatedtype, je suggère de déplacer la création de l'instance vers une implémentation par défaut de l'extension de protocole. De cette façon, les classes conformes n'auront pas à l'implémenter, nous évitant ainsi la duplication de code:

protocol Initializable {
    init()
}

protocol Creatable: Initializable {
    associatedtype Object: Initializable = Self
    static func newInstance() -> Object
}

extension Creatable {
    static func newInstance() -> Object {
        return Object()
    }
}

class MyClass: Creatable {
    required init() {}
}

class MyOtherClass: Creatable {
    required init() {}
}

// Any class (struct, etc.) conforming to Creatable
// can create new instances without having to implement newInstance() 
let instance1 = MyClass.newInstance()
let instance2 = MyOtherClass.newInstance()
0
Au Ris

Je lance juste mon chapeau dans le ring ici. Nous avions besoin d'un protocole renvoyant une option du type sur lequel le protocole était appliqué. Nous voulions aussi que le remplacement retourne explicitement le type, pas seulement Self.

L'astuce consiste plutôt à utiliser 'Self' comme type de retour. Vous définissez plutôt un type associé que vous définissez comme étant Self, puis utilisez ce type associé.

Voici l'ancienne façon, en utilisant Self ...

protocol Mappable{
    static func map() -> Self?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> Self? {
        ...
    }
}

Voici la nouvelle façon en utilisant le type associé. Notez que le type de retour est explicite maintenant, pas 'Self'.

protocol Mappable{
    associatedtype ExplicitSelf = Self
    static func map() -> ExplicitSelf?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> SomeSpecificClass? {
        ...
    }
}
0
MarqueIV