web-dev-qa-db-fra.com

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

Disons que j'ai le protocole suivant:

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

Et que j'ai les structs suivants:

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

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

Comme vous pouvez le constater, je devais "me conformer" au protocole identifiable de la structure A et de la structure B. Mais imaginons si j'avais plus de structures devant se conformer à ce protocole ... Je ne veux pas copier/coller 'la conformité (var id: Int, nom de la variable: String)

Je crée donc un extension de protocole:

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

Avec cette extension, je peux maintenant créer une structure conforme au protocole identifiable sans avoir à implémenter les deux propriétés:

struct C: Identifiable {

}

Maintenant, le problème est que je ne peux pas définir de valeur pour 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 ne sont que getting. 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 change 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 définis une nouvelle valeur sur id ou name, la valeur par défaut (getter) est obtenue. Bien sûr, le passeur est vide.

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

Merci d'avance.

53
Axort

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

Je peux vous montrer quelques alternatives.

Sous-classement (programmation orientée objet)

Le moyen le plus simple (comme vous l’imaginez probablement déjà) consiste à utiliser des classes au lieu de struct.

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

class A: IdentifiableBase { }

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

Inconvénients: dans ce cas, votre classe A doit hériter de IdentifiableBase et puisque dans 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 populaire dans le 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 }
    }
}

Maintenant, vous pouvez adapter votre type à Identifiable simplement en écrivant

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

Tester

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

Les protocoles et les extensions de protocole sont très puissants, mais ils ont tendance à être plus 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
Nick

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 de support telle que _x comme dans ObjC. Dans le code de solution ci-dessous, vous pouvez voir que xTimesTwo ne stocke rien, mais calcule simplement le résultat à partir de x.

Voir Documents officiels sur les propriétés calculées.

Les fonctionnalités que vous souhaitez peuvent également être des observateurs de propriété.

Les setters/getters sont différents de ceux d’Objective-C.

Ce dont vous avez besoin c'est:

var x:Int

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

Vous pouvez modifier d’autres propriétés dans le setter/getters, ce à quoi elles sont destinées

0
Harsh

Objets associés à Objective-C

Vous pouvez utiliser Objective-C objets associés pour ajouter en gros un stored property à un class ou 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)
        }   
    }
}

Maintenant, vous pouvez rendre votre class conforme à Identifiable en écrivant simplement

class A: Identifiable {

}

Tester

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

0
DominicMDev