Swift - PropertyWrapper
# 一、知识点
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 (opens new window) -- 对
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
}
}
# 三、资料
- 01
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 02
- Flutter - 轻松实现PageView卡片偏移效果09-08
- 03
- Flutter - 升级到3.24后页面还会多次rebuild吗?🧐08-11