SwiftUI 和 Swift 5.1 新特性(2) 屬性代理Property Delegates

SwiftUI 帶來的 Swift 5.1 的新特性比框架自己更重要。咱們能夠預見到,這些新的語言特性很快會被各個庫做者所使用。在上一篇中,咱們解釋了 SwiftUI 代碼中 some Viewsome 是什麼以及它爲什麼很重要 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

1. 包裝懶初始化邏輯

爲了實現屬性 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,有了這些約定後,編譯器能夠爲 MyTypetext 生成如下代碼:框架

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 均可以用。函數

2. 包裝防護性拷貝

咱們再來看一下一個防護性拷貝的例子,它基於 NSCopyingpost

@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,這也印證了咱們以前說的$pathpath的本質。可是這樣的語法畢竟有點難看,在不須要$path 出現的時候應該儘量隱藏它:線程

public struct MyType {
  @DefensiveCopying(withoutCopying: UIBezierPath())
  public var path: UIBezierPath
}
複製代碼

3. 包裝 UserDefaults 的存取

咱們常常須要將屬性寫成針對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

掃描下方二維碼,關注「面試官小健」

相關文章
相關標籤/搜索