In Xcode 11 Beta 1, the name of the modifier used in Swift was @PropertyDelegate, now it is @PropertyWrapper.
0258-property-wrappers
Property Wrappers are commonly used in SwiftUI, such as @state, @binding, @environment, @environmentobject, etc.
The Property Wrappers feature makes the code more concise and readable, reducing template code and giving users flexibility in customizations.
We saw the future of Swift, and it was full of
@
s.
Analysis of the
The implementation of the @ Lazy
Prior to Swift 5.1, lazy initialization of an attribute required adding the lazy keyword to the attribute, and lazy processing was hardcoded into a fixed pattern at compile time, with a range of support.
Use lazy to declare a lazily initialized property
lazy var foo = 1738
Copy the code
Without language level support, you would need to write a lot of boilerplate code like this to get the same effect:
struct Foo {
private var _foo: Int?
var foo: Int {
get {
if let value = _foo { return value }
let initialValue = 1738
_foo = initialValue
return initialValue
}
set {
_foo = newValue
}
}
}
Copy the code
With Property wrappers, you can simply declare as
@Lazy var foo = 1738
Copy the code
The use of @lazy is similar to that of Lazy, both simple and straightforward.
So how is the @Lazy property wrapper type implemented?
@propertyWrapper
enum Lazy<Value> {
case uninitialized(() -> Value)
case initialized(Value)
init(wrappedValue: @autoclosure @escaping() - >Value) {
self = .uninitialized(wrappedValue)
}
var wrappedValue: Value {
mutating get {
switch self {
case .uninitialized(let initializer):
let value = initializer()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
self = .initialized(newValue)
}
}
}
Copy the code
The property wrapper type provides storage for properties that are used as wrappers. The wrappedValue computed attribute provides the true implementation of the wrapper.
@Lazy var foo = 1738
Copy the code
Will be converted to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
Copy the code
We can provide a reset(_:) operation on Lazy to set it to the new value:
extension Lazy {
mutating func reset(_ newValue: @autoclosure @escaping() - >Value) {
self = .uninitialized(newValue)
}
}
_foo.reset(42)
Copy the code
We can add an initialization method.
extension Lazy {
init(body: @escaping() - >Value) {
self = .uninitialized(body)
}
}
func createAString(a) -> String { . }
@Lazy var bar: String // not initialized yet
_bar = Lazy(body: createAString)
Copy the code
The above code can be declared as a single statement:
@Lazy(body: createAString) var bar: String
Copy the code
At this point @lazy is already richer and more flexible than Lazy.
So is this the only thing the property wrapper can do? To better understand its use, analyze an @file.
@ the analysis of the Field
@propertyWrapper
public struct Field<Value: DatabaseValue> {
public let name: String
private var record: DatabaseRecord?
private var cachedValue: Value?
public init(name: String) {
self.name = name
}
public func configure(record: DatabaseRecord) {
self.record = record
}
public var wrappedValue: Value {
mutating get {
if cachedValue = = nil { fetch() }
return cachedValue!
}
set {
cachedValue = newValue
}
}
public func flush(a) {
if let value = cachedValue {
record!.flush(fieldName: name, value)
}
}
public mutating func fetch(a) {
cachedValue = record!.fetch(fieldName: name, type: Value.self)}}Copy the code
We can define our model based on the Field attribute wrapper:
public struct Person: DatabaseModel {
@Field(name: "first_name") public var firstName: String
@Field(name: "last_name") public var lastName: String
@Field(name: "date_of_birth") public var birthdate: Date
}
Copy the code
File allows us to refresh the existing value, get the new value, and retrieve the name of the corresponding field in the database.
@Field(name: "first_name") public var firstName: String
Copy the code
After expansion:
private var _firstName: Field<String> = Field(name: "first_name")
public var firstName: String {
get { _firstName.wrappedValue }
set { _firstName.wrappedValue = newValue }
}
Copy the code
Since _firstName is private, this property is not externally accessible and can not be used to provide methods.
@propertyWrapper
public struct Field<Value: DatabaseValue> {
// ... API as before ...
/ / new
public var projectedValue: Self {
get { self }
set { self = newValue }
}
}
Copy the code
After adding projectedValue,
@Field(name: "first_name") public var firstName: String
Copy the code
It’s going to expand to
private var _firstName: Field<String> = Field(name: "first_name")
public var firstName: String {
get { _firstName.wrappedValue }
set { _firstName.wrappedValue = newValue }
}
public var $firstName: Field<String> {
get { _firstName.projectedValue }
set { _firstName.projectedValue = newValue }
}
Copy the code
Projection Properties
ProjectedValue is called Projection Properties, so firstName’s Projection property is $firstName, and it is visible wherever firstName is visible. The projection property is prefixed with $.
With the projection property, we can happily use the following operations
somePerson.firstName = "Taylor"
$somePerson.flush()
Copy the code
This feature has been extensively used in vapor/fluent-kit’s 1.0.0-alpha.3.
To learn more about property wrappers, let’s continue with a few examples.
For example,
Delayed Initialization
variable
@propertyWrapper
struct DelayedMutable<Value> {
private var _value: Value? = nil
var wrappedValue: Value {
get {
guard let value = _value else {
fatalError("property accessed before being initialized")}return value
}
set {
_value = newValue
}
}
/// "Reset" the wrapper so it can be initialized again.
mutating func reset(a) {
_value = nil}}Copy the code
immutable
@propertyWrapper
struct DelayedImmutable<Value> {
private var _value: Value? = nil
var wrappedValue: Value {
get {
guard let value = _value else {
fatalError("property accessed before being initialized")}return value
}
// Perform an initialization, trapping if the
// value is already initialized.
set {
if _value ! = nil {
fatalError("property initialized twice")
}
_value = newValue
}
}
}
Copy the code
NSCopying
@propertyWrapper
struct Copying<Value: NSCopying> {
private var _value: Value
init(wrappedValue value: Value) {
// Copy the value on initialization.
self._value = value.copy() as! Value
}
var wrappedValue: Value {
get { return _value }
set {
// Copy the value on reassignment.
_value = newValue.copy() as! Value}}}Copy the code
User defaults
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
enum GlobalSettings {
@UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
static var isFooFeatureEnabled: Bool
@UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
static var isBarFeatureEnabled: Bool
}
Copy the code
AtomicWrite
@propertyWrapper
public struct AtomicWrite<Value> {
// TODO: Faster version with os_unfair_lock?
let queue = DispatchQueue(label: "Atomic write access queue", attributes: .concurrent)
var storage: Value
public init(initialValue value: Value) {
self.storage = value
}
public var wrappedValue: Value {
get {
return queue.sync { storage }
}
set {
queue.sync(flags: .barrier) { storage = newValue }
}
}
/// Atomically mutate the variable (read-modify-write).
///
/// - parameter action: A closure executed with atomic in-out access to the wrapped property.
public mutating func mutate(_ mutation: (inout Value) throws -> Void) rethrows {
return try queue.sync(flags: .barrier) {
try mutation(&storage)
}
}
}
Copy the code
Trimmed
public struct Trimmed {
private var storage: String!
private let characterSet: CharacterSet
public var wrappedValue: String {
get { storage }
set { storage = newValue.trimmingCharacters(in: characterSet) }
}
public init(initialValue: String) {
self.characterSet = .whitespacesAndNewlines
wrappedValue = initialValue
}
public init(initialValue: String.characterSet: CharacterSet) {
self.characterSet = characterSet
wrappedValue = initialValue
}
}
Copy the code
@Trimmed
var text = " \n Hello, World! \n\n "
print(text) // "Hello, World!"
// By default trims white spaces and new lines, but it also supports any character set
@Trimmed(characterSet: .whitespaces)
var text = " \n Hello, World! \n\n "
print(text) // "\n Hello, World! \n\n"
Copy the code
More strategy, which can be reference guillermomuntaner/Burritos
Property Wrappers have some restrictions
- Properties Can’t Participate in Error Handling
- Wrapped Properties Can’t Be Aliased
- Property Wrappers Are Difficult To Compose
- Property Wrappers Aren’t First-Class Dependent Types
- Property Wrappers Are Difficult to Document
- Property Wrappers Further Complicate Swift
From Swift Property Wrappers.
conclusion
Property wrappers certainly simplify the code. In terms of usage, we can customize various access policies, so there is more room for imagination. Because these policies are essentially constraints on the data store, the robustness and security of the code increases.
To read more, please follow OldBirds’ official wechat account