Swift | 属性包装器
1. 什么是 Swift Property Wrapper?
Swift Property Wrapper 是一种特性,它允许我们在声明属性时添加自定义的包装逻辑。通过使用属性包装器,我们可以在不修改类或结构体定义的情况下,定制属性的访问和存储方式。这种特性在很多场景下非常有用,例如:属性验证、类型转换、延迟初始化等。
2. 属性包装器的定义与使用
要定义一个属性包装器,我们需要创建一个实现了特定协议的结构体或类。Swift 提供了 @propertyWrapper
属性包装器特性来帮助我们定义包装器。下面是一个完整的示例:
swift
@propertyWrapper
struct MyWrapper {
var wrappedValue: Int {
willSet {
// 自定义包装逻辑
print("Value changing to: \(wrappedValue)")
}
didSet {
// 自定义包装逻辑
print("Value changed to: \(wrappedValue)")
}
}
init(wrappedValue: Int) {
self.wrappedValue = wrappedValue
}
var projectedValue: Self {
return self
}
}
struct MyStruct {
@MyWrapper(wrappedValue: 10)
public var myProperty: Int
}
var instance = MyStruct()
instance.myProperty = 20 // 输出:Value changed to: 20
instance.$myProperty // 等于:projectedValue
编程接口
willSet
:设置新值之前调用。didSet
:新值设置完成调用。projectedValue
:可以使用instance.$myProperty
拿到projectedValue
值,方便我们添加前缀、后缀、验证器验证结果等。
3. 演示
3.1. 范围限制
swift
@propertyWrapper
struct RangeLimited {
var wrappedValue: Int {
didSet {
if wrappedValue < lowerBound {
wrappedValue = lowerBound
} else if wrappedValue > upperBound {
wrappedValue = upperBound
}
}
}
let lowerBound: Int
let upperBound: Int
init(wrappedValue: Int, range: ClosedRange<Int>) {
self.lowerBound = range.lowerBound
self.upperBound = range.upperBound
self.wrappedValue = wrappedValue
}
var projectedValue: Self {
return self
}
}
struct MyStruct {
@RangeLimited(range: 0...100) var myProperty: Int = 0
}
var instance = MyStruct()
instance.myProperty = 150
print("myProperty value: \(instance.myProperty) lowerBound: \(instance.$myProperty.lowerBound) upperBound: \(instance.$myProperty.upperBound)") // 输出: myProperty value: 100 lowerBound: 0 upperBound: 100
3.2. 用户名验证器
swift
@propertyWrapper
struct MyUsernameValidator {
var wrappedValue: String {
didSet {
self.isValided = wrappedValue.count >= self.minLength && wrappedValue.count <= self.maxLength
}
}
var isValided: Bool = false
var minLength: Int
var maxLength: Int
init(wrappedValue: String, minLength: Int, maxLength: Int) {
self.wrappedValue = wrappedValue
self.minLength = minLength
self.maxLength = maxLength
}
var projectedValue: Self { self }
}
struct MyStruct {
@MyUsernameValidator(wrappedValue: "", minLength: 3, maxLength: 10) public var myUsername: String
}
var instance = MyStruct()
print("myUsername: \(instance.myUsername) isValided: \(instance.$myUsername.isValided)") // myUsername: isValided: false
instance.myUsername = "yimt"
print("myUsername: \(instance.myUsername) isValided: \(instance.$myUsername.isValided)") // myUsername: yimt isValided: true
4. 注意事项
在使用属性包装器时,需要注意以下几点:
- 属性包装器只能用于类或结构体的属性,不能用于全局变量或局部变量。
- 尽量保持属性包装器的逻辑简洁明了,不要在包装器中实现复杂的业务逻辑。
- 谨慎使用属性包装器,过度使用可能会增加代码复杂性和难以维护。