En termes simples, un wrapper de propriété est une structure générique qui encapsule l'accès en lecture et en écriture à la propriété et lui ajoute un comportement supplémentaire. Nous l'utilisons si nous avons besoin de contraindre les valeurs de propriété disponibles, d'ajouter une logique supplémentaire à l'accès en lecture / écriture (comme l'utilisation de bases de données ou les valeurs par défaut de l'utilisateur), ou d'ajouter des méthodes supplémentaires.
Cet article concerne une nouvelle approche Swift 5.1 des propriétés d'encapsulation, qui introduit une nouvelle syntaxe plus propre.
qu'est-ce qu'un bureau pmo
Imaginez que vous développez une application et que vous disposez d'un objet contenant des données de profil utilisateur.
struct Account { var firstName: String var lastName: String var email: String? } let account = Account(firstName: 'Test', lastName: 'Test', email: ' [email protected] ') account.email = ' [email protected] ' print(account.email)
Vous souhaitez ajouter une vérification par e-mail. Si l'adresse e-mail de l'utilisateur n'est pas valide, le email
la propriété doit être nil
. Ce serait un bon cas pour utiliser un wrapper de propriété pour encapsuler cette logique.
struct Email { private var _value: Value? init(initialValue value: Value?) { _value = value } var value: Value? { get { return validate(email: _value) ? _value : nil } set { _value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let regex = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-za-z]{2,64}' let pred = NSPredicate(format: 'SELF MATCHES %@', regex) return pred.evaluate(with: email) } }
Nous pouvons utiliser ce wrapper dans la structure du compte:
struct Account { var firstName: String var lastName: String var email: Email }
Désormais, nous sommes sûrs que la propriété email ne peut contenir qu'une adresse e-mail valide.
Tout semble bon, sauf la syntaxe.
let account = Account(firstName: 'Test', lastName: 'Test', email: Email(initialValue: ' [email protected] ')) account.email.value = ' [email protected] ' print(account.email.value)
Avec un wrapper de propriété, la syntaxe d'initialisation, de lecture et d'écriture de ces propriétés devient plus complexe. Alors, est-il possible d'éviter cette complication et d'utiliser des wrappers de propriété sans changements de syntaxe? Avec Swift 5.1, la réponse est oui.
Swift 5.1 fournit une solution plus élégante pour créer des wrappers de propriété, où le marquage d'un wrapper de propriété avec un @propertyWrapper
l'annotation est autorisée. Ces wrappers ont une syntaxe plus compacte que les traditionnels, ce qui donne un code plus compact et compréhensible. Le @propertyWrapper
l'annotation n'a qu'une seule exigence: votre objet wrapper doit contenir une propriété non statique appelée a wrappedValue
.
@propertyWrapper struct Email { var value: Value? var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-Za-z]{2,64}' let emailPred = NSPredicate(format:'SELF MATCHES %@', emailRegEx) return emailPred.evaluate(with: email) } }
Pour définir une telle propriété encapsulée dans le code, nous devons utiliser la nouvelle syntaxe.
@Email var email: String?
Nous avons donc marqué la propriété avec l'annotation @. Le type de propriété doit correspondre au type d'encapsuleur `wrappedValue`. Maintenant, vous pouvez travailler avec cette propriété comme avec l'ordinaire.
email = ' [email protected] ' print(email) // [email protected] email = 'invalid' print(email) // nil
Génial, c'est mieux maintenant qu'avec l'ancienne approche. Mais notre implémentation d'encapsuleur présente un inconvénient: elle n'autorise pas de valeur initiale pour la valeur encapsulée.
@Email var email: String? = ' [email protected] ' //compilation error.
Pour résoudre ce problème, nous devons ajouter l'initialiseur suivant au wrapper:
init(wrappedValue value: Value?) { self.value = value }
Et c'est tout.
@Email var email: String? = ' [email protected] ' print(email) // [email protected] @Email var email: String? = 'invalid' print(email) // nil
Le code final du wrapper est ci-dessous:
@propertyWrapper struct Email { var value: Value? init(wrappedValue value: Value?) { self.value = value } var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-Za-z]{2,64}' let emailPred = NSPredicate(format:'SELF MATCHES %@', emailRegEx) return emailPred.evaluate(with: email) } }
Prenons un autre exemple. Vous écrivez un jeu et vous disposez d'une propriété dans laquelle les scores des utilisateurs sont stockés. La condition est que cette valeur doit être supérieure ou égale à 0 et inférieure ou égale à 100. Vous pouvez y parvenir en utilisant un wrapper de propriété.
@propertyWrapper struct Scores { private let minValue = 0 private let maxValue = 100 private var value: Int init(wrappedValue value: Int) { self.value = value } var wrappedValue: Int { get { return max(min(value, maxValue), minValue) } set { value = newValue } } } @Scores var scores: Int = 0
Ce code fonctionne mais il ne semble pas générique. Vous ne pouvez pas le réutiliser avec des contraintes différentes (pas 0 et 100). De plus, il ne peut contraindre que des valeurs entières. Il serait préférable d'avoir un wrapper configurable qui peut contraindre tout type conforme au protocole Comparable. Pour rendre notre wrapper configurable, nous devons ajouter tous les paramètres de configuration via un initialiseur. Si l'initialiseur contient un wrappedValue
attribut (la valeur initiale de notre propriété), il doit être le premier paramètre.
@propertyWrapper struct Constrained { private var range: ClosedRange private var value: Value init(wrappedValue value: Value, _ range: ClosedRange) { self.value = value self.range = range } var wrappedValue: Value { get { return max(min(value, range.upperBound), range.lowerBound) } set { value = newValue } } }
Pour initialiser une propriété encapsulée, nous définissons tous les attributs de configuration entre parenthèses après l'annotation.
la loi sur le steagall en verre abroger les conséquences
@Constrained(0...100) var scores: Int = 0
Le nombre d'attributs de configuration est illimité. Vous devez les définir entre parenthèses dans le même ordre que dans l'initialiseur.
Si vous avez besoin d'accéder au wrapper lui-même (pas à la valeur encapsulée), vous devez ajouter un trait de soulignement avant le nom de la propriété. Par exemple, prenons la structure de notre compte.
struct Account { var firstName: String var lastName: String @Email var email: String? } let account = Account(firstName: 'Test', lastName: 'Test', email: ' [email protected] ') account.email // Wrapped value (String) account._email // Wrapper(Email)
Nous avons besoin d'accéder au wrapper lui-même afin d'utiliser les fonctionnalités supplémentaires que nous lui avons ajoutées. Par exemple, nous voulons que la structure du compte soit conforme au protocole Equatable. Deux comptes sont égaux si leurs adresses e-mail sont égales, et les adresses e-mail doivent être insensibles à la casse.
extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs.email?.lowercased() == rhs.email?.lowercased() } }
Cela fonctionne, mais ce n'est pas la meilleure solution car nous devons nous rappeler d'ajouter une méthode en minuscules () partout où nous comparons les e-mails. Un meilleur moyen serait de rendre la structure du courrier électronique équivalente:
extension Email: Equatable { static func ==(lhs: Email, rhs: Email) -> Bool { return lhs.wrappedValue?.lowercased() == rhs.wrappedValue?.lowercased() } }
et comparez les wrappers au lieu des valeurs encapsulées:
extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs._email == rhs._email } }
Le @propertyWrapper
l'annotation fournit un autre sucre de syntaxe - une valeur projetée. Cette propriété peut avoir le type de votre choix. Pour accéder à cette propriété, vous devez ajouter un $
préfixe au nom de la propriété. Pour expliquer son fonctionnement, nous utilisons un exemple du framework Combine.
Le @Published
property wrapper crée un éditeur pour la propriété et le renvoie en tant que valeur projetée.
@Published var message: String print(message) // Print the wrapped value $message.sink { print(Comment aborder les wrappers pour les propriétés Swift
En termes simples, un wrapper de propriété est une structure générique qui encapsule l'accès en lecture et en écriture à la propriété et lui ajoute un comportement supplémentaire. Nous l'utilisons si nous avons besoin de contraindre les valeurs de propriété disponibles, d'ajouter une logique supplémentaire à l'accès en lecture / écriture (comme l'utilisation de bases de données ou les valeurs par défaut de l'utilisateur), ou d'ajouter des méthodes supplémentaires.
Cet article concerne une nouvelle approche Swift 5.1 des propriétés d'encapsulation, qui introduit une nouvelle syntaxe plus propre.
Imaginez que vous développez une application et que vous disposez d'un objet contenant des données de profil utilisateur.
struct Account { var firstName: String var lastName: String var email: String? } let account = Account(firstName: 'Test', lastName: 'Test', email: ' [email protected] ') account.email = ' [email protected] ' print(account.email)
Vous souhaitez ajouter une vérification par e-mail. Si l'adresse e-mail de l'utilisateur n'est pas valide, le email
la propriété doit être nil
. Ce serait un bon cas pour utiliser un wrapper de propriété pour encapsuler cette logique.
struct Email { private var _value: Value? init(initialValue value: Value?) { _value = value } var value: Value? { get { return validate(email: _value) ? _value : nil } set { _value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let regex = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-za-z]{2,64}' let pred = NSPredicate(format: 'SELF MATCHES %@', regex) return pred.evaluate(with: email) } }
Nous pouvons utiliser ce wrapper dans la structure du compte:
struct Account { var firstName: String var lastName: String var email: Email }
Désormais, nous sommes sûrs que la propriété email ne peut contenir qu'une adresse e-mail valide.
Tout semble bon, sauf la syntaxe.
let account = Account(firstName: 'Test', lastName: 'Test', email: Email(initialValue: ' [email protected] ')) account.email.value = ' [email protected] ' print(account.email.value)
Avec un wrapper de propriété, la syntaxe d'initialisation, de lecture et d'écriture de ces propriétés devient plus complexe. Alors, est-il possible d'éviter cette complication et d'utiliser des wrappers de propriété sans changements de syntaxe? Avec Swift 5.1, la réponse est oui.
Swift 5.1 fournit une solution plus élégante pour créer des wrappers de propriété, où le marquage d'un wrapper de propriété avec un @propertyWrapper
l'annotation est autorisée. Ces wrappers ont une syntaxe plus compacte que les traditionnels, ce qui donne un code plus compact et compréhensible. Le @propertyWrapper
l'annotation n'a qu'une seule exigence: votre objet wrapper doit contenir une propriété non statique appelée a wrappedValue
.
@propertyWrapper struct Email { var value: Value? var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-Za-z]{2,64}' let emailPred = NSPredicate(format:'SELF MATCHES %@', emailRegEx) return emailPred.evaluate(with: email) } }
Pour définir une telle propriété encapsulée dans le code, nous devons utiliser la nouvelle syntaxe.
@Email var email: String?
Nous avons donc marqué la propriété avec l'annotation @. Le type de propriété doit correspondre au type d'encapsuleur `wrappedValue`. Maintenant, vous pouvez travailler avec cette propriété comme avec l'ordinaire.
email = ' [email protected] ' print(email) // [email protected] email = 'invalid' print(email) // nil
Génial, c'est mieux maintenant qu'avec l'ancienne approche. Mais notre implémentation d'encapsuleur présente un inconvénient: elle n'autorise pas de valeur initiale pour la valeur encapsulée.
@Email var email: String? = ' [email protected] ' //compilation error.
Pour résoudre ce problème, nous devons ajouter l'initialiseur suivant au wrapper:
init(wrappedValue value: Value?) { self.value = value }
Et c'est tout.
@Email var email: String? = ' [email protected] ' print(email) // [email protected] @Email var email: String? = 'invalid' print(email) // nil
Le code final du wrapper est ci-dessous:
@propertyWrapper struct Email { var value: Value? init(wrappedValue value: Value?) { self.value = value } var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = '[A-Z0-9a-z._%+-] [email protected] [A-Za-z0-9.-]+\.[A-Za-z]{2,64}' let emailPred = NSPredicate(format:'SELF MATCHES %@', emailRegEx) return emailPred.evaluate(with: email) } }
Prenons un autre exemple. Vous écrivez un jeu et vous disposez d'une propriété dans laquelle les scores des utilisateurs sont stockés. La condition est que cette valeur doit être supérieure ou égale à 0 et inférieure ou égale à 100. Vous pouvez y parvenir en utilisant un wrapper de propriété.
@propertyWrapper struct Scores { private let minValue = 0 private let maxValue = 100 private var value: Int init(wrappedValue value: Int) { self.value = value } var wrappedValue: Int { get { return max(min(value, maxValue), minValue) } set { value = newValue } } } @Scores var scores: Int = 0
Ce code fonctionne mais il ne semble pas générique. Vous ne pouvez pas le réutiliser avec des contraintes différentes (pas 0 et 100). De plus, il ne peut contraindre que des valeurs entières. Il serait préférable d'avoir un wrapper configurable qui peut contraindre tout type conforme au protocole Comparable. Pour rendre notre wrapper configurable, nous devons ajouter tous les paramètres de configuration via un initialiseur. Si l'initialiseur contient un wrappedValue
attribut (la valeur initiale de notre propriété), il doit être le premier paramètre.
@propertyWrapper struct Constrained { private var range: ClosedRange private var value: Value init(wrappedValue value: Value, _ range: ClosedRange) { self.value = value self.range = range } var wrappedValue: Value { get { return max(min(value, range.upperBound), range.lowerBound) } set { value = newValue } } }
Pour initialiser une propriété encapsulée, nous définissons tous les attributs de configuration entre parenthèses après l'annotation.
@Constrained(0...100) var scores: Int = 0
Le nombre d'attributs de configuration est illimité. Vous devez les définir entre parenthèses dans le même ordre que dans l'initialiseur.
Si vous avez besoin d'accéder au wrapper lui-même (pas à la valeur encapsulée), vous devez ajouter un trait de soulignement avant le nom de la propriété. Par exemple, prenons la structure de notre compte.
struct Account { var firstName: String var lastName: String @Email var email: String? } let account = Account(firstName: 'Test', lastName: 'Test', email: ' [email protected] ') account.email // Wrapped value (String) account._email // Wrapper(Email)
Nous avons besoin d'accéder au wrapper lui-même afin d'utiliser les fonctionnalités supplémentaires que nous lui avons ajoutées. Par exemple, nous voulons que la structure du compte soit conforme au protocole Equatable. Deux comptes sont égaux si leurs adresses e-mail sont égales, et les adresses e-mail doivent être insensibles à la casse.
extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs.email?.lowercased() == rhs.email?.lowercased() } }
Cela fonctionne, mais ce n'est pas la meilleure solution car nous devons nous rappeler d'ajouter une méthode en minuscules () partout où nous comparons les e-mails. Un meilleur moyen serait de rendre la structure du courrier électronique équivalente:
extension Email: Equatable { static func ==(lhs: Email, rhs: Email) -> Bool { return lhs.wrappedValue?.lowercased() == rhs.wrappedValue?.lowercased() } }
et comparez les wrappers au lieu des valeurs encapsulées:
extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs._email == rhs._email } }
Le @propertyWrapper
l'annotation fournit un autre sucre de syntaxe - une valeur projetée. Cette propriété peut avoir le type de votre choix. Pour accéder à cette propriété, vous devez ajouter un $
préfixe au nom de la propriété. Pour expliquer son fonctionnement, nous utilisons un exemple du framework Combine.
Le @Published
property wrapper crée un éditeur pour la propriété et le renvoie en tant que valeur projetée.
@Published var message: String print(message) // Print the wrapped value $message.sink { print($0) } // Subscribe to the publisher
Comme vous pouvez le voir, nous utilisons un message pour accéder à la propriété encapsulée et un message $ pour accéder à l'éditeur. Que devez-vous faire pour ajouter une valeur projetée à votre wrapper? Rien de spécial, il suffit de le déclarer.
@propertyWrapper struct Published { private let subject = PassthroughSubject() var wrappedValue: Value { didSet { subject.send(wrappedValue) } } var projectedValue: AnyPublisher { subject.eraseToAnyPublisher() } }
Comme indiqué précédemment, le projectedValue
propriété peut avoir n'importe quel type en fonction de vos besoins.
La syntaxe des nouveaux wrappers de propriétés semble bonne, mais elle contient également plusieurs limitations, les principales étant:
throws
. Par exemple, dans notre Email
Par exemple, il n'est pas possible de générer une erreur si un utilisateur tente de définir un e-mail non valide. Nous pouvons retourner nil
ou plantez l'application avec un fatalError()
appel, ce qui pourrait être inacceptable dans certains cas.@CaseInsensitive
wrapper et combinez-le avec un @Email
wrapper au lieu de créer le @Email
emballage insensible à la casse. Mais de telles constructions sont interdites et conduisent à des erreurs de compilation.@CaseInsensitive @Email var email: String?
Pour contourner ce cas particulier, nous pouvons hériter de Email
wrapper du CaseInsensitive
wrapper. Cependant, l'héritage a également des limitations: seules les classes prennent en charge l'héritage et une seule classe de base est autorisée.
@propertyWrapper
les annotations simplifient la syntaxe des encapsuleurs de propriétés et nous pouvons opérer avec les propriétés encapsulées de la même manière qu'avec les propriétés ordinaires. Cela rend votre code, en tant que Développeur Swift plus compact et compréhensible. En même temps, il présente plusieurs limites dont nous devons tenir compte. J'espère que certains d'entre eux seront rectifiés dans les futures versions de Swift.
Si vous souhaitez en savoir plus sur les propriétés Swift, consultez les documents officiels .
Un wrapper de propriété est une structure générique qui encapsule l'accès en lecture et en écriture à la propriété et lui ajoute un comportement supplémentaire.
Nous utilisons des wrappers de propriété si nous avons besoin de contraindre les valeurs de propriété disponibles, de modifier l'accès en lecture / écriture (comme l'utilisation de la base de données ou d'un autre stockage) ou d'ajouter des méthodes supplémentaires telles que la validation de valeur.
L'annotation @propertyWrapper est disponible dans Swift 5.1 ou version ultérieure.
Ils ne peuvent pas participer à la gestion des erreurs et l'application de plusieurs wrappers à la propriété n'est pas autorisée.
Comme vous pouvez le voir, nous utilisons un message pour accéder à la propriété encapsulée et un message $ pour accéder à l'éditeur. Que devez-vous faire pour ajouter une valeur projetée à votre wrapper? Rien de spécial, il suffit de le déclarer.
@propertyWrapper struct Published { private let subject = PassthroughSubject() var wrappedValue: Value { didSet { subject.send(wrappedValue) } } var projectedValue: AnyPublisher { subject.eraseToAnyPublisher() } }
Comme indiqué précédemment, le projectedValue
propriété peut avoir n'importe quel type en fonction de vos besoins.
La syntaxe des nouveaux wrappers de propriétés semble bonne, mais elle contient également plusieurs limitations, les principales étant:
throws
. Par exemple, dans notre Email
Par exemple, il n'est pas possible de générer une erreur si un utilisateur tente de définir un e-mail non valide. Nous pouvons retourner nil
ou plantez l'application avec un fatalError()
appel, ce qui pourrait être inacceptable dans certains cas.@CaseInsensitive
wrapper et combinez-le avec un @Email
wrapper au lieu de créer le @Email
emballage insensible à la casse. Mais de telles constructions sont interdites et conduisent à des erreurs de compilation.@CaseInsensitive @Email var email: String?
Pour contourner ce cas particulier, nous pouvons hériter de Email
wrapper du CaseInsensitive
wrapper. Cependant, l'héritage a également des limitations: seules les classes prennent en charge l'héritage et une seule classe de base est autorisée.
@propertyWrapper
les annotations simplifient la syntaxe des encapsuleurs de propriétés et nous pouvons opérer avec les propriétés encapsulées de la même manière qu'avec les propriétés ordinaires. Cela rend votre code, en tant que Développeur Swift plus compact et compréhensible. En même temps, il présente plusieurs limites dont nous devons tenir compte. J'espère que certains d'entre eux seront rectifiés dans les futures versions de Swift.
Si vous souhaitez en savoir plus sur les propriétés Swift, consultez les documents officiels .
Un wrapper de propriété est une structure générique qui encapsule l'accès en lecture et en écriture à la propriété et lui ajoute un comportement supplémentaire.
Nous utilisons des wrappers de propriété si nous avons besoin de contraindre les valeurs de propriété disponibles, de modifier l'accès en lecture / écriture (comme l'utilisation de la base de données ou d'un autre stockage) ou d'ajouter des méthodes supplémentaires telles que la validation de valeur.
L'annotation @propertyWrapper est disponible dans Swift 5.1 ou version ultérieure.
Ils ne peuvent pas participer à la gestion des erreurs et l'application de plusieurs wrappers à la propriété n'est pas autorisée.