🔰property wrapper

把真正的資料包裹起來,只開放一個或兩個公開窗口的結構。

Swifttypeproperty ⟩ wrapper

緣起

如果我們要設計一個「學生」的結構,然後有「分數」這個屬性,但規定不管我們如何輸入這個分數,這個分數都只能介於 0 ~ 100 之間。例如:

我們輸入的分數

學生實際的分數

說明

-200

0

最低只能 0 分。

60

60

如果是正常的分數,就不改變。

300

100

最高只能 100 分。

如果輸入程式碼的話,要長這樣:

var student = Student()

student.grade = 500
print(student.grade)        // 結果是:100

student.grade = 60
print(student.grade)        // 結果是:60

student.grade = -20
print(student.grade)        // 結果是:0

這時要如何設計這個結構 (struct) 呢?首先,嘗試下面的程式碼:

只要這樣設計,加入一個「私密變數」(private var)、再一個「計算屬性」(computed property),就可以達到我們原先的要求:「可以控管學生的分數始終在 0 ~ 100 之間」。

任務完美達成,還有問題嗎❓

這樣做雖然沒什麼問題,但問題在於:在程式設計中,類似這樣的模式 (pattern) 實在太常出現,例如:「分數在 0 ~ 100 之間」、「評分在 0 ~ 10 之間」、「pH 值在 -1 到 16 之間」、「顏色值在 0 ~ 255 之間」等等,這些一再出現的類似模式,如果每次遇到的時候,都要重新寫一次,那就違反了程式設計的 DRY 綱領:「 Don't Repeat Yourself❗️」,而這也就是為什麼 Swift 要在 5.1 的版本中加入 Property Wrapper 的功能了,Property Wrapper 可以讓我們只設計一次,就解決上面所有的類似模式,不需每次遇到時就重寫一次。

為何叫 Property Wrapper❓

你只要仔細觀察上面「控管學生分數」的例子就會發現:真正的分數是被藏在私密變數 _grade 中,外界無法直接存取這個變數,而是必須透過計算屬性(computed property) grade 來存取它,換句話說,真正的分數等於是被包起來(wrapped)了,而計算屬性 grade 某種程度說來,就是包住它的人(wrapper),而這也就是為什麼 Swift 要將這個新功能稱為:「Property Wrapper」的原因了。

下面我來說明:如何用 Property Wrapper 來達成上面所說的「控管學生的分數」。

設計第一個 Property Wrapper

首先,我們必須先設計一個可以「控管學生的分數」的結構(struct),設計的方式跟上面的計算屬性 grade 很類似:👉 程式碼:paiza.io

這樣設計,有何好處❓

這樣設計的好處是:如果我們有很多科的分數都要登記,例如:國文、英文、數學等,在沒有 property wrapper 之前,我們必須為每一科的分數寫一個計算屬性(computed property),才能確保每個分數都在 0 ~ 100 之間。

但有了 property wrapper 之後就不需要了,因為我們已經將「確保分數都在 0 ~ 100 之間」這樣的機制寫在 @propertyWrapper Grade 之中,所以只要在每科分數的變數前面加上 @Grade ,註明我們要用相同的機制來控管這些分數就可以,如下:

留下的問題

  • 當我們用 @propertyWrapper Grade (後面開始都簡稱為 @Grade) 來控管一個分數屬性時,Swift 到底背後做了什麼魔法❓

  • 當我們設計了 @Grade ,雖然可以控管分數,卻也只能控管「0 ~ 100」的分數。如果我們要控管其他範圍的數字,如:pH 值、顏色值等等,又該如何❓

這些問題且待我們有空時,繼續分解。🚧

Swift compiler 為 property wrapper 做的兩件事

⚠️ 以下內容尚未整理,請自行斟酌觀看❗️

一個 property wrapper 就是把真正的資料 (圖中的 _value) 包裹起來,只開放一個或兩個公開的窗口 (圖中的 wrappedValueprojectedValue) 來存取這個資料的結構 (通常是 struct,但也可以是 class, enum 等其他型別)。

註:圖中程式碼只用於解說,無實際用途。

目前只能存活在別的結構裡面,當作別人的屬性,還不能獨立存在,所以稱為 Property Wrapper。

以一當三

以上圖為例,我們為 Person 這個結構添加了一個 property wrapper - @Wrapped var name,但實際上,Swift compiler 會在背後自動產生三個屬性 (如果我們有定義 projectedValue 的話):

  • name:這是公開屬性,直接與 wrappedValue 對接,用 person.name 就等同於存取 property wrapper 的 wrappedValue。

  • $name:也是公開屬性,直接與 projectedValue 對接,用 person.$name 等同於存取 property wrapper 的 projectedValue,但此值是有宣告才有,不是每個 property wrapper 都必備。

  • _name:這是 Person 的私密屬性,代表 property wrapper 本身,只能在 Person 內部使用,在外部並不能person._name 來存取。

重點

  • 只能用 var 宣告,不能用 let

  • 可以在別的 property wrapper 裡面當屬性來用,例如:@UnitInterval

可以用來做什麼❓

  • 自動編碼 (Codable):👉

目前不支援

函數內區域變數

廣域變數

範例

Projected Value

#todo

@State 變數projectedValue 所傳回的值是 @State 變數本身 (self)。 👉 Property Wrappers in Swift - Swift by Sundell

The @Published property wrapper uses its projectedValue to expose a publisher. 👉 Wrapping your head around Property Wrappers in Swift - Donny Wals

Last updated

Was this helpful?