欢迎关注微信公众号:FSA全栈行动 👋
一、知识点
Property Wrapper,即属性包装器,其作用是将属性的
定义代码
与属性的存储方式代码
进行分离,抽取的管理的存储代码
只需要编写一次,即可将功能应用于其它属性上。
1、基础用法
功能需求:确保值始终小于或等于12
这里我们直接使用 property wrapper
进行封装演示
@propertyWrapper
struct TwelveOrLess {
private var number: Int
// wrappedValue变量的名字是固定的
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
init() {
self.number = 0
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height) // 0
rectangle.height = 10
print(rectangle.height) // 10
rectangle.height = 24
print(rectangle.height) // 12
这里可以注意到,在创建 SmallRectangle
实例时,并不需要初始化 height
和 width
原因:
被 property wrapper
声明的属性,实际上在存储时的类型是 TwelveOrLess
,只不过编译器施了一些魔法,让它对外暴露的类型依然是被包装的原来的类型。
上面的 SmallRectangle
结构体,等同于下方这种写法
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
2、设置初始值
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
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)
}
}
使用了 @SmallNumber
但没有指定初始化值
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width) // 0 0
使用了 @SmallNumber
,并指定初始化值
这里会调用 init(wrappedValue:)
方法
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width) // 1 1
使用@SmallNumber,并传参进行初始化
这里会调用 init(wrappedValue:maximum:)
方法
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()
print(narrowRectangle.height, narrowRectangle.width) // 2 3
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width) // 5 4
3、projectedValue
projectedValue
为property wrapper
提供了额外的功能(如:标志某个状态,或者记录property wrapper
内部的变化等)两者都是通过实例的属性名进行访问,唯一不同的地方在于,
projectedValue
需要在属性名前加上$
才可以访问
wrappedValue
:实例.属性名
projectedValue
:实例.$属性名
下面的代码将一个 projectedValue
属性添加到 SmallNumber
结构中,以在存储该新值之前跟踪该属性包装器是否调整了该属性的新值。
@propertyWrapper
struct SmallNumber1 {
private var number: Int
var projectedValue: Bool
init() {
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
这里的 someStructure.$someNumber
访问的是 projectedValue
4、使用限制
- 不能在协议里的属性使用
protocol SomeProtocol {
// Property 'sp' declared inside a protocol cannot have a wrapper
@SmallNumber1 var sp: Bool { get set }
}
- 不能在 extension 内使用
extension SomeStructure {
// Extensions must not contain stored properties
@SmallNumber1 var someProperty2: Int
}
- 不能在
enum
内使用
enum SomeEnum {
// Property wrapper attribute 'SmallNumber1' can only be applied to a property
@SmallNumber1 case a
case b
}
class
里的wrapper property
不能覆盖其他的 property
class AClass {
@SmallNumber1 var aProperty: Int
}
class BClass: AClass {
// Cannot override with a stored property 'aProperty'
override var aProperty: Int = 1
}
wrapper
属性不能定义getter
或setter
方法
struct SomeStructure2 {
// Property wrapper cannot be applied to a computed property
@SmallNumber1 var someNumber: Int {
get {
return 0
}
}
}
wrapper
属性不能被lazy
、@NSCopying
、@NSManaged
、weak
、 或者unowned
修饰
二、实际应用
Foil -- 对UserDefaults进行了轻量级的属性包装第三方库
这部分我们主要简单的看下该第三方库的核心实现与使用
1、使用
- 声明
// 声明使用的key为flagEnabled,默认值为true
@WrappedDefault(key: "flagEnabled", defaultValue: true)
var flagEnabled: Bool
// 声明使用的key为timestamp
@WrappedDefaultOptional(key: "timestamp")
var timestamp: Date?
- 获取
// 获取变量在UserDefault中对应存储的值
self.flagEnabled
self.timestamp
- 赋值
// 设置UserDefault中对应存储的值
self.flagEnabled = false
self.timestamp = Date()
2、核心代码
WrappedDefault.swift
文件
@propertyWrapper
public struct WrappedDefault<T: UserDefaultsSerializable> {
private let _userDefaults: UserDefaults
/// 使用UserDefaults是所使用的key
public let key: String
/// 从UserDefaults中获取到的值
public var wrappedValue: T {
get {
self._userDefaults.fetch(self.key)
}
set {
self._userDefaults.save(newValue, for: self.key)
}
}
// 初始化方法
public init(
keyName: String,
defaultValue: T,
userDefaults: UserDefaults = .standard
) {
self.key = keyName
self._userDefaults = userDefaults
// 对key所对应的值进行初始化(已有值则跳过,没有则进行初始化)
userDefaults.registerDefault(value: defaultValue, key: keyName)
}
}
WrappedDefaultOptional.swift
文件
@propertyWrapper
public struct WrappedDefaultOptional<T: UserDefaultsSerializable> {
private let _userDefaults: UserDefaults
public let key: String
/// 从UserDefaults中获取到的值,无则返回nil
public var wrappedValue: T? {
get {
self._userDefaults.fetchOptional(self.key)
}
set {
if let newValue = newValue {
// 更新值
self._userDefaults.save(newValue, for: self.key)
} else {
// 删除值
self._userDefaults.delete(for: self.key)
}
}
}
public init(keyName: String,
userDefaults: UserDefaults = .standard) {
self.key = keyName
self._userDefaults = userDefaults
}
}