SwiftUI 带来的 Swift 5.1 的新特性比框架自己更重要。咱们能够预见到,这些新的语言特性很快会被各个库做者所使用。在上一篇中,咱们解释了 SwiftUI 代码中 some View
的 some
是什么以及它为什么很重要 SwiftUI 和 Swift 5.1 新特性(1) 不透明返回类型 Opaque Result Type。在这篇中,咱们须要一块儿学习下 Swift UI 中 @State
和 @Binding
的准备知识, 这种标记的本质是属性代理(Property Delegates),也叫属性包装器(Property Wrappers)。代码以下:面试
struct OrderForm : View { @State private var order: Order var body: some View { Stepper(value: $order.quantity, in: 1...10) { Text("Quantity: \(order.quantity)") } } } 复制代码
这个语言特性很是通用,任何对于属性的存取有“套路”的访问,均可以用它来包装这种“套路”。咱们先来学习一下几个套路。swift
为了实现属性 text
为懒初始化的属性,咱们能够写成以下代码:markdown
public struct MyType { var textStorage: String? = nil public var text: String { get { guard let value = textStorage else { fatalError("text has not yet been set!") } return value } set { textStorage = newValue } } } 复制代码
然而若是有不少属性都是这样的逻辑,这样的写法是很冗余的。因此属性代理就是解决这个问题的:app
@propertyDelegate public struct LateInitialized<Value> { private var storage: Value? public init() { storage = nil } public var value: Value { get{ guard let value = storage else { fatalError("value has not yet been set!") } return value } set { storage = newValue } } } // 应用属性代理 LateInitialized public struct MyType { @LateInitialized public var text: String? } 复制代码
属性代理 LateInitialized
是一个泛型类型,它自己用 @propertyDelegate
修饰,它必须有一个叫 value
的属性类型为 Value
,有了这些约定后,编译器能够为 MyType
的 text
生成如下代码:框架
public struct MyType { var $text: LateInitialized<String> = LateInitialized<String>() public var text: String { get { $text.value } set { $text.value = newValue} } } 复制代码
能够看到,通过属性代理包装事后的 text
,编译器帮助生成了一个存储属性为 $text
,类型就是这个属性代理,而 text
自己变成了一个计算属性。你们可能以为 $text
属性是编译器生成的,因此不能够访问,事实偏偏相反,text
和 $text
均可以用。函数
咱们再来看一下一个防护性拷贝的例子,它基于 NSCopying
post
@propertyDelegate public struct DefensiveCopying<Value: NSCopying> { private var storage: Value public init(initialValue value: Value) { storage = value.copy() as! Value } public var value: Value { get { storage } set { storage = newValue.copy() as! Value } } } // 应用属性代理 DefensiveCopying public struct MyType { @DefensiveCopying public var path: UIBezierPath = UIBezierPath() } 复制代码
属性代理 DefensiveCopying
的不一样点在于它的初始化函数 init(initialValue:)
,这个函数因为编译器的约定,因此必定得叫这个名字。与上个例子同样,编译器会生成存储属性 $path
,并用初始值初始化。学习
这里咱们吹毛求疵一下,UIBezierPath
被强制拷贝了一次,因此咱们再提供一个属性代理的初始化函数,并应用它:spa
// DefensiveCopying 中增长 public init(withoutCopying value: Value) { storage = value } // 应用不拷贝的初始化函数 public struct MyType { @DefensiveCopying public var path: UIBezierPath init() { $path = DefensiveCopying(withoutCopying: UIBezierPath()) } } 复制代码
在应用的部分咱们看到能够像初始化一个通常变量同样初始化$path
,这也印证了咱们以前说的$path
和path
的本质。可是这样的语法毕竟有点难看,在不须要$path
出现的时候应该尽量隐藏它:线程
public struct MyType { @DefensiveCopying(withoutCopying: UIBezierPath()) public var path: UIBezierPath } 复制代码
咱们常常须要将属性写成针对UserDefaults存取的计算属性,而这个通用访问策略也能用属性代理实现:
@propertyDelegate struct UserDefault<T> { let key: String let defaultValue: T var value: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } // 应用属性代理 UserDefault enum GlobalSettings { @UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false) static var isFooFeatureEnabled: Bool @UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false) static var isBarFeatureEnabled: Bool } 复制代码
全部对于属性访问策略的抽象,均可以使用属性代理来实现,咱们还能够想到 Thread-local storage(线程本地存储)属性存取、原子属性存取、Copy-on-write 属性存取、引用包装类型属性的存取均可以使用属性代理来实现。固然 SwiftUI 的 @State
和 @Binding
也是属性代理,要详细解释它们,还须要一些 Swift 的知识,咱们在下一篇中,给你们详细说一说。
相关文章:
SwiftUI 和 Swift 5.1 新特性(1) 不透明返回类型 Opaque Result Type
扫描下方二维码,关注“面试官小健”