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
掃描下方二維碼,關注「面試官小健」