属性包装特性给了我们一个机会,可以在一定程度上简化语言的模板代码,并且通过“标注”的方式来改变特性。
它与自定义 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 可以让属性具有引用语义的原因。
网友评论