What is Property Wrappers

Property wrapper, one of Swift’s features. Used to modify attributes, it can extract logic about attribute duplication to simplify code. Such as thread safety checks, storing data to a database, and so on.

How to use

Identified by @propertyWrapper. Here is an example of how it is used.

The basic use

Suppose you have a SmallRectangle structure with two properties, height and width, and you need to limit the values of both properties to less than 12.

According to the above requirements, we can first create a TwelveOrLess structure:

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
Copy the code

Using the SmallRectangle structure:

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"
Copy the code

As you can see from the code above, attributes decorated with @twelveorless can automatically limit values to 12 and below.

So what happens when we use @twelveorless var height: Int?

TwelveOrLess = TwelveOrLess() var TwelveOrLess = TwelveOrLess() var height: TwelveOrLess = TwelveOrLess() var height: TwelveOrLess() Int { get { return _height.wrappedValue } set { _height.wrappedValue = newValue } }Copy the code

Rectangle. Height = 24

  • Call the set function for SmallRectangle Height.
  • Call the set function of TwelveOrLess wrappedValue.
  • callnumber = min(newValue, 12)To ensure that the new value is less than or equal to 12.

Setting the initial value

Argument passed to call that takes no arguments:

struct SmallRectangle {
    @TwelveOrLess var height: Int = 1
    @TwelveOrLess var width: Int = 1
}
Copy the code

This is because TwelveOrLess does not provide an initialization function that takes arguments. TwelveOrLess TwelveOrLess can be resolved by adding an initialization function that takes arguments:

init(wrappedValue: Int) {
    number = min(12, wrappedValue)
}
Copy the code

If our condition variable 12 can be set dynamically, we can add an initialization function to set the condition variable:

init(wrappedValue: Int, maximum: Int) {
    self.maximum = maximum
    number = min(maximum, wrappedValue)
}
Copy the code

Init (wrappedValue: Int, maximum: Int)

struct NarrowRectangle {
    @TwelveOrLess(wrappedValue: 2, maximum: 5) var height: Int
    @TwelveOrLess(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
Copy the code

Tips: Init () is used by default when a variable decorated with @twelveorless is not initialized.

struct ZeroRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
Copy the code

ProjectedValue

ProjectedValue is used to get some additional state values for your defining logic.

For example, in the example above, if you want to find out if the value you set exceeds the maximum value, you can use projectedValue to get this.

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    private var maximum: Int
    var projectedValue: Bool
    
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
    
    init() {
        projectedValue = false
        self.number = 0
        self.maximum = 12
    }
    
    init(wrappedValue: Int) {
        projectedValue = false
        maximum = 12
        number = min(maximum, wrappedValue)
    }
    
    init(wrappedValue: Int, maximum: Int) {
        projectedValue = false
        self.maximum = maximum
        number = min(maximum, wrappedValue)
    }
}
Copy the code

Get the status value:

struct SomeStructure {
    @TwelveOrLess var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
Copy the code

Get the projectedValue as $+ attribute name.

When set to 4, there is no trigger condition greater than 12, so $someNumber is false; When set to 55, greater than 12 triggers the condition, so $someNumber is true.

Actual project application

  • Lowercase all string characters:
@propertyWrapper
struct LowerLetter {
    private var value = ""
    var wrappedValue: String {
        set {
            value = newValue.lowercased()
        }
        get { return value}
    }
    
    init(wrappedValue: String) {
        value = wrappedValue.lowercased()
    }
}

struct Person {
    @LowerLetter var name: String
}

let p = Person(name: "ABCd")
p.name // abcd
Copy the code
  • Automatic weak self:
// declare @propertyWrapper public final class Delegated1<Input> {public init() {self.callback = {_ in}} private var callback: (Input) -> Void public var wrappedValue: (Input) -> Void { return callback } public var projectedValue: Delegated1<Input> { return self } } public extension Delegated1 { func delegate<Target: AnyObject>( to target: Target, with callback: @escaping (Target, Input) -> Void) { self.callback = { [weak target] input in guard let target = target else { return } return Callback (target, input)}}} // Use final class TextField {@delegated1 var didUpdate: (String) -> () } let textField = TextField() textField.$didUpdate.delegate(to: self) { (self, text) in // `self` is weak automatically! self.label.text = text }Copy the code