75 votes

Swift - Extensions de protocole - Valeurs par défaut des propriétés

Supposons que je dispose du protocole suivant :

protocol Identifiable {
    var id: Int {get}
    var name: String {get}
}

Et que j'ai les structures suivantes :

struct A: Identifiable {
    var id: Int
    var name: String
}

struct B: Identifiable {
    var id: Int
    var name: String
}

Comme vous pouvez le voir, j'ai dû me conformer au protocole Identifiable dans les structures A et B. Mais imaginez que j'ai N autres structures qui doivent se conformer à ce protocole... Je ne veux pas 'copier/coller' la conformité (var id : Int, var name : String)

Je crée donc un extension du protocole :

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

Grâce à cette extension, je peux désormais créer une structure conforme au protocole Identifiable sans avoir à implémenter les deux propriétés :

struct C: Identifiable {

}

Le problème est que je ne peux pas donner de valeur à la propriété id ou à la propriété name :

var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property

Cela est dû au fait que, dans le protocole Identifiable, l'identifiant et le nom sont uniquement accessibles. Maintenant, si je change les propriétés id et name en {get set} J'obtiens l'erreur suivante :

Le type "C" n'est pas conforme au protocole "Identifiable".

Cette erreur se produit parce que je n'ai pas implémenté de setter dans l'extension de protocole... Je modifie donc l'extension de protocole :

extension Identifiable {
    var id: Int {
        get {
            return 0
        }

        set {

        }
    }

    var name: String {
        get {
            return "default"
        }

        set {

        }
    }
}

Maintenant, l'erreur disparaît, mais si je donne une nouvelle valeur à id ou à name, c'est la valeur par défaut qui est utilisée (getter). Bien entendu, le setter est vide .

Ma question est la suivante : Quel morceau de code dois-je placer à l'intérieur du setter ? Car si j'ajoute self.id = newValue il se bloque (récursif).

Merci d'avance.

107voto

appzYourLife Points 10219

Il semble que vous souhaitiez ajouter un stored property à un type via une extension de protocole. Cependant, cela n'est pas possible car les extensions ne permettent pas d'ajouter une propriété stockée.

Je peux vous montrer quelques alternatives.

Sous-classement (programmation orientée objet)

La méthode la plus simple (comme vous l'imaginez probablement déjà) consiste à utiliser des classes au lieu de structures.

class IdentifiableBase {
    var id = 0
    var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name  = "test"
print(a.name) // test

Inconvénient : dans ce cas, votre classe A doit hériter de IdentifiableBase et comme en Swift il n'y a pas d'héritage multiple, ce sera la seule classe dont A pourra hériter.

Composants (Programmation orientée protocole)

Cette technique est très répandue dans le domaine du développement de jeux

struct IdentifiableComponent {
    var id = 0
    var name = "default"
}

protocol HasIdentifiableComponent {
    var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
    var id: Int {
        get { return identifiableComponent.id }
        set { identifiableComponent.id = newValue }
    }
    var name: String {
        get { return identifiableComponent.name }
        set { identifiableComponent.name = newValue }
    }
}

Vous pouvez maintenant faire en sorte que votre type soit conforme à Identifiable écrire simplement

struct A: Identifiable {
    var identifiableComponent = IdentifiableComponent()
}

Test

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

0 votes

Merci pour votre réponse. J'aime beaucoup cette technique des composants. J'essaie d'adopter la programmation orientée protocole à partir de maintenant et je cherchais donc quelque chose de ce genre.

1 votes

@Axort : De rien. Je vous suggère également de regarder ceci vidéo à propos de Programmation orientée protocole de la WWDC 2015. Très intéressant.

10 votes

Pourquoi le protocole HasIdentifiableComponent ? Pourquoi ne pas simplement faire protocol Identifiable: var identifiableComponent: IdentifiableComponent { get set } Dans ce cas extension Identifiable { var id: Int { get { return identifiableComponent.id } set { identifiableComponent.id = newValue } } etc... }

6voto

DominicMDev Points 39

Objets associés en Objective-C

Vous pouvez utiliser Objectif-C des objets associés afin d'ajouter un stored property à un class o protocol . Notez que les objets associés ne fonctionnent que pour les objets de classe.

import ObjectiveC.runtime

protocol Identifiable: class {
    var id: Int { get set }
    var name: String { get set }
}

var IdentifiableIdKey   = "kIdentifiableIdKey"
var IdentifiableNameKey = "kIdentifiableNameKey"

extension Identifiable {
    var id: Int {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }

    var name: String {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableNameKey) as? String) ?? "default"
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }
}

Vous pouvez désormais créer votre class se conformer à Identifiable en écrivant simplement

class A: Identifiable {

}

Test

var a = A()
print(a.id)    // 0
print(a.name)  // default
a.id = 5
a.name = "changed"
print(a.id)    // 5
print(a.name)  // changed

2voto

Nick Points 1936

Les protocoles et les extensions de protocoles sont très puissants, mais ils sont surtout utiles pour les propriétés et les fonctions en lecture seule.

pour ce que vous essayez d'accomplir (propriétés stockées avec une valeur par défaut), les classes et l'héritage pourraient en fait être la solution la plus élégante

quelque chose comme :

class Identifiable {
    var id: Int = 0
    var name: String = "default"
}

class A:Identifiable {
}

class B:Identifiable {
}

let a = A()

print("\(a.id) \(a.name)")

a.id = 42
a.name = "foo"

print("\(a.id) \(a.name)")

2 votes

Merci pour votre réponse. Je cherchais une solution avec la programmation orientée protocole au lieu de la programmation orientée objet.

0voto

Harsh Points 682

C'est pourquoi vous n'avez pas pu définir les propriétés.

La propriété devient une propriété calculée, ce qui signifie qu'elle n'a pas de variable d'appui telle que _x, comme c'est le cas dans ObjC. Dans le code de la solution ci-dessous, vous pouvez voir que xTimesTwo ne stocke rien, mais calcule simplement le résultat à partir de x.

Voir la documentation officielle sur les propriétés calculées.

La fonctionnalité que vous recherchez pourrait également être Observateurs de propriété.

Les éléments de réglage et d'acquisition sont différents de ce qu'ils étaient en Objective-C.

Ce qu'il vous faut, c'est

var x:Int

var xTimesTwo:Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

Vous pouvez modifier d'autres propriétés à l'intérieur du setter/getters, ce qui est leur raison d'être

0 votes

Merci pour votre réponse. Oui, comme vous l'avez dit, le problème est que vous ne pouvez pas déclarer des vars dans une extension (les extensions ne peuvent pas contenir de propriétés stockées). Je me demandais donc s'il y avait un moyen (une petite chance) avec des propriétés calculées :P

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