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 the
enum
use
enum SomeEnum {
// Property wrapper attribute 'SmallNumber1' can only be applied to a property
@SmallNumber1 case a
case b
}
Copy the code
class
In thewrapper property
Cannot 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
wrapper
Property not definablegetter
 或setter
methods
struct SomeStructure2 {
// Property wrapper cannot be applied to a computed property
@SmallNumber1 var someNumber: Int {
get {
return 0}}}Copy the code
wrapper
Properties cannot belazy
,@NSCopying
,@NSManaged
,weak
, orunowned
modified
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