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