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
爲懶初始化的屬性,咱們能夠寫成以下代碼:app
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
}
}
}
複製代碼
然而若是有不少屬性都是這樣的邏輯,這樣的寫法是很冗餘的。因此屬性代理就是解決這個問題的:框架
@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
均可以用。post
咱們再來看一下一個防護性拷貝的例子,它基於 NSCopying
學習
@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
,並用初始值初始化。ui
這裏咱們吹毛求疵一下,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
掃描下方二維碼,關注「面試官小健」