Swift @propertyWrapper

作者: 一粒咸瓜子 | 来源:发表于2021-12-31 17:14 被阅读0次

    属性包装特性给了我们一个机会,可以在一定程度上简化语言的模板代码,并且通过“标注”的方式来改变特性。
    它与自定义 getter 和 setter 做的事情相似,只不过功能更强大,且不需要到处重复去写一样的代码。

    说简单点: 通过 @propertyWrapper 可以移除掉一些重复或者类似的代码。
    在 Swift 中,这一特性的正式名称是属性包装 (Property Wrapper)。

    使用

    @propertyWrapper
    struct FileStorage<T: Codable> {
        var value: T?
        let directory: FileManager.SearchPathDirectory
        let fileName: String
        
        init(directory: FileManager.SearchPathDirectory, fileName: String) {
            value = try? FileHelper.loadJSON(from: directory, fileName: fileName)
            self.directory = directory
            self.fileName = fileName
        }
        
        var wrappedValue: T? {
            set {
                value = newValue
                if let value = newValue {
                    try? FileHelper.writeJSON(value, to: directory, fileName: fileName)
                } else {
                    try? FileHelper.delete(from: directory, fileName: fileName)
                }
            }
            get {
                value
            }
        }
    }
    
    // 使用
    @FileStorage(directory: .documentDirectory, fileName: "user.json")
    var loginUser: User?
    
    @propertyWrapper
    struct UserDefaultStorage<T> {
        let key: String
        let initialValue: T
        
        init(_ initialValue: T, key: String) {
            self.key = key
            self.initialValue = initialValue
        }
        
        var wrappedValue: T {
            set {
                UserDefaults.standard.set(newValue, forKey: key)
                UserDefaults.standard.synchronize()
            }
            get {
                UserDefaults.standard.object(forKey: key) as? T ?? initialValue
            }
        }
    }
    
    // 使用
    // UserDefault 无法保存枚举值,只能保存对应的 rawValue,所以在外面包一层映射
    // 使用 rawValue 的条件是标明其类型:
    enum Sorting: Int {
      //...
    }
    
    var sorting: Sorting {
        set { sortingRawValue = newValue.rawValue }
        get { Sorting(rawValue: sortingRawValue) ?? .id }
    }
    @UserDefaultStorage(Sorting.id.rawValue, key: "sorting")
    var sortingRawValue: Sorting.RawValue
    @UserDefaultStorage(true, key: "showEnglishName")
    var showEnglishName: Bool
    @UserDefaultStorage(false, key: "showFavoriteOnly")
    var showFavoriteOnly: Bool
    

    Combine 中的包装属性

    在 Combine 中不论是 @State@Binding@ObjectBinding @EnvironmentObject,它们都是被 @propertyWrapper 修饰的 struct 类型。

    Combine 中 State 定义的关键部分如下:

    @propertyWrapper
    public struct State<Value> :
    DynamicViewProperty, BindingConvertible
    {
        // init(initialValue:),wrappedValue 和 projectedValue 构成了一个 propertyWrapper 最重要的部分。
        public init(initialValue value: Value)
        public var value: Value { get nonmutating set }
        public var wrappedValue: Value { get nonmutating set }
        public var projectedValue: Binding<Value> { get }
    }
    
    • initialValue 这个参数名相对特殊:当它出现在 init 方法的第一个参数位置时,编译器将允许我们在声明的时候直接为 @State var brain 进行赋值。

    • 对 包装属性 进行赋值,看起来也就和普通的变量赋值没有区别。但是,实际上这些调用都触发的是属性包装中的 wrappedValue。

    • 使用$符号前缀访问,其实访问的是 projectedValue 属性。在 State 中,这个属性返回一个 Binding 类型的值,通过遵守 BindingConvertible,State 暴露了修改其内部存储的方法,这也就是为什么传递 Binding 可以让属性具有引用语义的原因。

    相关文章

      网友评论

        本文标题:Swift @propertyWrapper

        本文链接:https://www.haomeiwen.com/subject/cytnqrtx.html