Wenn Sie SwiftUI verwendet haben, haben Sie wahrscheinlich auf Schlüsselwörter wie @ObservedObject, @EnvironmentObject, @FetchRequest usw. geachtet. Property Wrapper (im Folgenden als "Property Wrapper" bezeichnet) ist eine neue Funktion von Swift 5.1. In diesem Artikel erfahren Sie, woher alle Konstruktionen von @ stammen und wie Sie sie in SwiftUI und in Ihren Projekten verwenden.

Übersetzt von: Evgeny Zavozhansky, Entwickler von FunCorp.
Hinweis: Zum Zeitpunkt der Erstellung der Übersetzung hatte ein Teil des Quellcodes des Originalartikels aufgrund von Änderungen in der Sprache seine Relevanz verloren, sodass einige Codebeispiele absichtlich ersetzt wurden.
Property Wrapper wurden erstmals im März 2019 in Swift- Foren vorgestellt, einige Monate vor der Ankündigung von SwiftUI. In seinem ursprünglichen Vorschlag beschrieb Douglas Gregor, ein Mitglied des Swift Core-Teams, dieses Konstrukt (damals als Eigenschaftendelegierte bezeichnet) als "eine benutzerfreundliche Verallgemeinerung der Funktionalität, die derzeit von einem Sprachkonstrukt wie z. B. lazy
bereitgestellt wird".
Wenn eine Eigenschaft mit dem Schlüsselwort lazy
deklariert wird, bedeutet dies, dass sie beim ersten Zugriff initialisiert wird. Beispielsweise könnte eine verzögerte Eigenschaftsinitialisierung unter Verwendung einer privaten Eigenschaft implementiert werden, auf die über eine berechnete Eigenschaft zugegriffen wird. Die Verwendung des Schlüsselworts " lazy
erleichtert dies jedoch erheblich.
struct Structure {
SE-0258: Property Wrapper erklärt den Entwurf und die Implementierung von Property Wrappern perfekt. Anstatt zu versuchen, die Beschreibung in der offiziellen Dokumentation zu verbessern, sollten Sie einige Beispiele betrachten, die mit Eigenschaftenwrappern implementiert werden können:
- Einschränkung von Eigenschaftswerten;
- Umwandlung von Werten beim Ändern von Eigenschaften;
- Gleichheitssemantik ändern und Eigenschaften vergleichen;
- Eigenschaftenzugriffsprotokollierung.
Eigenschaftswerte begrenzen
SE-0258: Property Wrapper enthält mehrere praktische Beispiele, darunter @Clamping
, @Copying
, @Atomic
, @ThreadSpecific
, @Box
, @UserDefault
. Betrachten Sie den @Clamping
Wrapper, mit dem Sie den Maximal- oder Minimalwert einer Eigenschaft begrenzen können.
@propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(initialValue value: Value, _ range: ClosedRange<Value>) { precondition(range.contains(value)) self.value = value self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } }
@Clamping
kann beispielsweise der Säuregehalt einer Lösung modelliert werden, deren Wert einen Wert von 0 bis 14 annehmen kann.
struct Solution { @Clamping(0...14) var pH: Double = 7.0 } let carbonicAcid = Solution(pH: 4.68)
Wenn Sie versuchen, den pH-Wert außerhalb des Bereichs von (0...14)
einzustellen, nimmt die Eigenschaft den Wert an, der dem minimalen oder maximalen Intervall am nächsten liegt.
let superDuperAcid = Solution(pH: -1) superDuperAcid.pH
Eigenschaftenwrapper können verwendet werden, um andere Eigenschaftenwrapper zu implementieren. Beispielsweise begrenzt der Wrapper @UnitInterval
den Wert einer Eigenschaft mithilfe von @Clamping(0...1)
auf das Intervall @Clamping(0...1)
:
@propertyWrapper struct UnitInterval<Value: FloatingPoint> { @Clamping(0...1) var wrappedValue: Value = .zero init(initialValue value: Value) { self.wrappedValue = value } }
Ähnliche Ideen
@Positive
/ @NonNegative
gibt an, dass der Wert entweder eine positive oder eine negative Zahl sein kann.@NonZero
gibt an, dass der Wert der Eigenschaft nicht 0 sein kann.@Validated
oder @Whitelisted
/ @Blacklisted
schränkt den Wert einer Eigenschaft auf bestimmte Werte ein.
Konvertieren von Werten beim Ändern von Eigenschaften
Das Validieren von Textfeldwerten bereitet Anwendungsentwicklern ständig Kopfzerbrechen. Es gibt so viele Dinge zu beachten: von Plattitüden wie der Codierung bis zu böswilligen Versuchen, einen Code über ein Textfeld einzugeben. Verwenden Sie einen Eigenschaftenwrapper, um Leerzeichen zu entfernen, die ein Benutzer am Anfang und Ende einer Zeile eingegeben hat.
import Foundation let url = URL(string: " https:
Foundation
bietet die trimmingCharacters(in:)
-Methode an, mit der Sie Leerzeichen am Anfang und Ende einer Zeile entfernen können. Sie können diese Methode immer dann aufrufen, wenn Sie die Richtigkeit der Eingabe gewährleisten müssen, dies ist jedoch nicht sehr praktisch. Hierfür können Sie den Property Wrapper verwenden.
import Foundation @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { return value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(initialValue: String) { self.wrappedValue = initialValue } }
struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "…") quine.title
Ähnliche Ideen
@Transformed
wendet die ICU-Konvertierung auf die @Transformed
.@Rounded
/ @Truncated
rundet oder @Truncated
einen Zeichenfolgenwert ab.
Ändern Sie die Semantik von Gleichheit und Eigenschaftsvergleich
In Swift sind zwei Saiten gleich, wenn sie kanonisch äquivalent sind , d. H. enthalten die gleichen Zeichen. Angenommen, wir möchten, dass die Zeichenfolgeneigenschaften gleich sind und nicht zwischen Groß- und Kleinschreibung unterscheiden.
@CaseInsensitive
implementiert einen Wrapper für Eigenschaften vom Typ String
oder SubString
.
import Foundation @propertyWrapper struct CaseInsensitive<Value: StringProtocol> { var wrappedValue: Value } extension CaseInsensitive: Comparable { private func compare(_ other: CaseInsensitive) -> ComparisonResult { wrappedValue.caseInsensitiveCompare(other.wrappedValue) } static func == (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedSame } static func < (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedAscending } static func > (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedDescending } }
let hello: String = "hello" let HELLO: String = "HELLO" hello == HELLO
Ähnliche Ideen
@Approximate
für einen groben Vergleich von Eigenschaften des Typs Double oder Float.@Ranked
für Eigenschaften, deren Werte in der @Ranked
Reihenfolge sind (z. B. der Rang der Spielkarten).
Protokollierung des Eigenschaftenzugriffs
@Versioned
können Sie zugewiesene Werte abfangen und sich merken, wann sie festgelegt wurden.
import Foundation @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { defer { timestampedValues.append((Date(), value)) } value = newValue } } init(initialValue value: Value) { self.wrappedValue = value } }
Mit der ExpenseReport
Klasse können ExpenseReport
Zeitstempel der Verarbeitungszustände der Spesenabrechnung speichern.
class ExpenseReport { enum State { case submitted, received, approved, denied } @Versioned var state: State = .submitted }
Das obige Beispiel zeigt jedoch eine schwerwiegende Einschränkung in der aktuellen Implementierung von Eigenschaftenwrappern, die sich aus der Swift-Einschränkung ergibt: Eigenschaften können keine Ausnahmen auslösen. Wenn wir @Versioned
eine Einschränkung hinzufügen @Versioned
, um zu verhindern, dass der Wert nach dem .approved
des Werts .denied
in .approved
.denied
, ist fatalError()
die beste Option, die für echte Anwendungen nicht geeignet ist.
class ExpenseReport { @Versioned var state: State = .submitted { willSet { if newValue == .approved, $state.timestampedValues.map { $0.1 }.contains(.denied) { fatalError("") } } } } var tripExpenses = ExpenseReport() tripExpenses.state = .denied tripExpenses.state = .approved
Ähnliche Ideen
@Audited
, @Audited
Eigenschaftenzugriff zu @Audited
.@UserDefault
, um den Mechanismus zum Lesen und Speichern von Daten in UserDefaults
.
Einschränkungen
Eigenschaften können keine Ausnahmen auslösen
Wie bereits erwähnt, können Eigenschafts-Wrapper nur wenige Methoden zum Verarbeiten ungültiger Werte verwenden:
- ignoriere sie;
- Beenden Sie die Anwendung mit fatalError ().
Umbrochene Eigenschaften können nicht mit dem Attribut `typealias` gekennzeichnet werden
Das @UnitInterval
Beispiel @UnitInterval
, dessen Eigenschaft durch das Intervall (0...1)
, kann nicht als deklariert werden
typealias UnitInterval = Clamping(0...1)
Beschränkung der Verwendung von Zusammensetzungen aus mehreren Eigenschaftenverpackungen
Das Erstellen von Eigenschaftenwrappern ist keine kommutative Operation: Die Reihenfolge der Deklaration wirkt sich auf das Verhalten aus. Stellen Sie sich ein Beispiel vor, in dem die Eigenschaft slug, die die URL eines Blogposts darstellt, normalisiert ist. In diesem Fall hängt das Ergebnis der Normalisierung davon ab, wann die Leerzeichen vor oder nach dem Entfernen von Leerzeichen durch Bindestriche ersetzt werden. Daher wird eine Komposition mehrerer Property Wrapper derzeit nicht unterstützt.
@propertyWrapper struct Dasherized { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.replacingOccurrences(of: " ", with: "-") } } init(initialValue: String) { self.wrappedValue = initialValue } } struct Post { … @Dasherized @Trimmed var slug: String
Diese Einschränkung kann jedoch durch die Verwendung von Wrappern für verschachtelte Eigenschaften umgangen werden.
@propertyWrapper struct TrimmedAndDasherized { @Dasherized private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(initialValue: String) { self.wrappedValue = initialValue } } struct Post { … @TrimmedAndDasherized var slug: String }
Andere Einschränkungen für Eigenschaftenwrapper
- Kann nicht innerhalb des Protokolls verwendet werden.
- Eine Wrapper-Eigenschaftsinstanz kann nicht in
enum
deklariert werden. - Eine in einer Klasse deklarierte umschlossene Eigenschaft kann nicht von einer anderen Eigenschaft überschrieben werden.
- Eine
@NSCopying
Eigenschaft kann nicht lazy
, @NSCopying
, @NSManaged
, weak
oder nicht im unowned
. - Eine umschlossene Eigenschaft sollte die einzige in ihrer Definition sein (d. H.
@Lazy var (x, y) = /* ... */
). - Für eine umschlossene Eigenschaft kann kein
getter
und setter
definiert werden. - Die Typen der
wrappedValue
Eigenschaft und der wrappedValue
Variablen in init(wrappedValue:)
müssen dieselbe Zugriffsebene haben wie der Wrapper-Typ der Eigenschaft. - Die type-Eigenschaft von
projectedValue
muss dieselbe Zugriffsebene wie der Wrapper-Typ der Eigenschaft haben. init()
muss dieselbe Zugriffsebene wie der Property-Wrapper-Typ haben.
Lassen Sie uns zusammenfassen. Eigenschaftenwrapper in Swift ermöglichen Bibliotheksautoren den Zugriff auf das allgemeine Verhalten, das zuvor für Sprachfunktionen reserviert war. Ihr Potenzial zur Verbesserung der Lesbarkeit und zur Reduzierung der Codekomplexität ist riesig, und wir haben die Funktionen dieses Tools nur oberflächlich untersucht.
Verwenden Sie Property Wrapper in Ihren Projekten? Schreibe in die Kommentare!