Swift | 属性包装器

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

编程接口

  1. willSet:设置新值之前调用。
  2. didSet:新值设置完成调用。
  3. 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. 注意事项

在使用属性包装器时,需要注意以下几点:

  1. 属性包装器只能用于类或结构体的属性,不能用于全局变量或局部变量。
  2. 尽量保持属性包装器的逻辑简洁明了,不要在包装器中实现复杂的业务逻辑。
  3. 谨慎使用属性包装器,过度使用可能会增加代码复杂性和难以维护。
相关推荐
songgeb2 天前
YYImage Bug:一行之差,正常 JPEG 如何在 iOS 被读成条纹
ios·swift
fthux2 天前
Tick Rest,一个休息提醒工具的自我修养
macos·开源·swift
Fatbobman(东坡肘子)3 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
chaoahyl3 天前
WaterDiary 喝水记录 App 实战(2)· 实现喝水首页与本地保存
swift
茶底世界之下5 天前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
xbzb5 天前
Swift 对象存储建环实验 + Ceilometer 计量:两个收尾服务
swift·ring·实验·ceilometer·象存储·建环
2501_916008895 天前
iOS App 图标、启动屏和多语言搭配 方法,上架前使用KXBuild一次做好 App 图标
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918415 天前
iOS 推送通知怎么实现,从 APNs p8 密钥到 iOS 真机收到第一条推送
vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子5 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
人工智能·swiftui·swift
2501_916007477 天前
Windows OpenSSL 生成 iOS 证书,制作 p12 与描述文件,KXBuild 签名实操
windows·vscode·ios·objective-c·个人开发·swift·敏捷流程