Welcome to follow the official wechat account: FSA Full stack action 👋

I. Knowledge points

Property Wrapper is a Property Wrapper that separates the code defining the Property from the code storing the Property. The extracted managed storage code needs to be written only once to apply the functionality to other properties.

1. Basic usage

Functional requirement: Ensure that the value is always less than or equal to 12

Here we will use property Wrapper directly to demonstrate encapsulation

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    
    The name of the wrappedValue variable is fixed
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12)}}init(a) {
        self.number = 0}}struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle(a)print(rectangle.height) / / 0

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

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

Notice here that height and Width do not need to be initialized when creating a SmallRectangle instance

TwelveOrLess attributes declared by Property Wrapper are TwelveOrLess, but the compiler does some magic to expose them to the same type as they were wrapped. The SmallRectangle structure above is equivalent to the SmallRectangle structure below

struct SmallRectangle {
    private var _height = TwelveOrLess(a)private var _width = TwelveOrLess(a)var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}
Copy the code

2. Set the initial value

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

@smallNumber is used but no initialization value is specified

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

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

@smallNumber is used and the initialization value is specified

The init(wrappedValue:) method is called

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle(a)print(unitRectangle.height, unitRectangle.width) / / 1 1
Copy the code

Use @smallNumber and pass the parameter to initialize

The init(wrappedValue:maximum:) method is called

struct NarrowRectangle {
    Extra argument 'wrappedValue' in call
    // @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int = 1
    Init (wrappedValue:maximum:)
    // @SmallNumber(maximum: 9) var height: Int = 2
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

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

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

3, projectedValue

ProjectedValue provides additional functionality for the Property Wrapper (such as marking a state or recording changes within the Property Wrapper)

Both are accessed by the instance’s property name, the only difference being that projectedValue is accessed by prefacing the property name with $

  • wrappedValue: Instance. Property name
  • projectedValue: Instance.$Property name

The following code adds a projectedValue property to the SmallNumber structure to track whether the property wrapper has adjusted the new value of the property before storing it.

@propertyWrapper
struct SmallNumber1 {
    private var number: Int
    var projectedValue: Bool
    init(a) {
        self.number = 0
        self.projectedValue = false
    }
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false}}}}struct SomeStructure {
    @SmallNumber1 var someNumber: Int
}
var someStructure = SomeStructure()

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

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

SomeStructure.$someNumber accesses projectedValue

4. Restrictions on use

  • Cannot be used for properties in the protocol
protocol SomeProtocol {
    // Property 'sp' declared inside a protocol cannot have a wrapper
    @SmallNumber1 var sp: Bool { get set}}Copy the code
  • Cannot be used within Extension
extension SomeStructure {
    // Extensions must not contain stored properties
    @SmallNumber1 var someProperty2: Int
}
Copy the code
  • Not in theenumuse
enum SomeEnum {
    // Property wrapper attribute 'SmallNumber1' can only be applied to a property
    @SmallNumber1 case a
    case b
}
Copy the code
  • classIn thewrapper propertyCannot override other properties
class AClass {
    @SmallNumber1 var aProperty: Int
}

class BClass: AClass {
    // Cannot override with a stored property 'aProperty'
    override var aProperty: Int = 1
}
Copy the code
  • wrapperProperty not definablegetter 或 settermethods
struct SomeStructure2 {
    // Property wrapper cannot be applied to a computed property
    @SmallNumber1 var someNumber: Int {
        get {
            return 0}}}Copy the code
  • wrapperProperties cannot belazy,@NSCopying,@NSManaged,weak, orunownedmodified

Two, practical application

Foil — a lightweight third party library of properties wrapped around UserDefaults

In this section, we will briefly take a look at the core implementation and use of the third-party library

1, use,

  • The statement
// Declare that the key used is flagEnabled. The default value is true
@WrappedDefault(key: "flagEnabled", defaultValue: true)
var flagEnabled: Bool

// Declare the key to be timestamp
@WrappedDefaultOptional(key: "timestamp")
var timestamp: Date?
Copy the code
  • To obtain
// Get the value of the variable stored in UserDefault
self.flagEnabled
self.timestamp
Copy the code
  • The assignment
// Set the value to be stored in UserDefault
self.flagEnabled = false
self.timestamp = Date(a)Copy the code

2. Core code

WrappedDefault. Swift file

@propertyWrapper
public struct WrappedDefault<T: UserDefaultsSerializable> {
    private let _userDefaults: UserDefaults

    // use UserDefaults as the key used
    public let key: String

    // get the value from UserDefaults
    public var wrappedValue: T {
        get {
            self._userDefaults.fetch(self.key)
        }
        set {
            self._userDefaults.save(newValue, for: self.key)
        }
    }

    // Initialize method
    public init(
        keyName: String.defaultValue: T.userDefaults: UserDefaults = .standard
    ) {
        self.key = keyName
        self._userDefaults = userDefaults
        
        // Initialize the value corresponding to key (skip if there is a value, initialize if there is no value)
        userDefaults.registerDefault(value: defaultValue, key: keyName)
    }
}
Copy the code

WrappedDefaultOptional. Swift file

@propertyWrapper
public struct WrappedDefaultOptional<T: UserDefaultsSerializable> {
    private let _userDefaults: UserDefaults

    public let key: String

    /// Returns nil from UserDefaults
    public var wrappedValue: T? {
        get {
            self._userDefaults.fetchOptional(self.key)
        }
        set {
            if let newValue = newValue {
                / / update the value
                self._userDefaults.save(newValue, for: self.key)
            } else {
                / / delete values
                self._userDefaults.delete(for: self.key)
            }
        }
    }

    public init(keyName: String.userDefaults: UserDefaults = .standard) {
        self.key = keyName
        self._userDefaults = userDefaults
    }
}
Copy the code

Third, information

Swift Official Documents

apple / swift-evolution