• Swift | 属性包装器


    Swift | 属性包装器

    1. 什么是 Swift Property Wrapper?

    Swift Property Wrapper 是一种特性,它允许我们在声明属性时添加自定义的包装逻辑。通过使用属性包装器,我们可以在不修改类或结构体定义的情况下,定制属性的访问和存储方式。这种特性在很多场景下非常有用,例如:属性验证、类型转换、延迟初始化等。

    2. 属性包装器的定义与使用

    要定义一个属性包装器,我们需要创建一个实现了特定协议的结构体或类。Swift 提供了 @propertyWrapper 属性包装器特性来帮助我们定义包装器。下面是一个完整的示例:

    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    编程接口

    1. willSet:设置新值之前调用。
    2. didSet:新值设置完成调用。
    3. projectedValue:可以使用instance.$myProperty拿到projectedValue值,方便我们添加前缀、后缀、验证器验证结果等。

    3. 演示

    3.1. 范围限制

    @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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.2. 用户名验证器

    @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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    4. 注意事项

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

    1. 属性包装器只能用于类或结构体的属性,不能用于全局变量或局部变量。
    2. 尽量保持属性包装器的逻辑简洁明了,不要在包装器中实现复杂的业务逻辑。
    3. 谨慎使用属性包装器,过度使用可能会增加代码复杂性和难以维护。
  • 相关阅读:
    15. 从零开始编写一个类nginx工具, 如果将nginx.conf转成yaml,toml,json会怎么样
    C专家编程 第8章 为什么程序员无法分清万圣节和圣诞节 8.2 根据位模式构筑图形
    【Rust 基础篇】Rust中的不安全函数:解锁系统级编程的黑盒之门
    Flink中的时间和窗口(三)
    ElasticSearch中的中文分词详解
    C#与C++数据类型的相互转换。 C#使用非托管的dll,如何封送类,结构体、联合体,不同类型的数组、以及封送回调方法?
    基于SSM的网络教学(作业)管理系统
    通信协议(二)——SPI协议
    VS2015+windows10编译配置Ceres库
    适合弱电行业用的项目管理系统,找企智汇项目管理系统!
  • 原文地址:https://blog.csdn.net/yimtcode/article/details/133623063