譯自 Cupcake Corner: Introduction
更多內容,歡迎關注公眾號 「Swift花園」
喜歡文章?不如來個 ??➕三連?關注專欄,關注我 ???
Cupcake Corner:介紹
在這個專案中,我們會構建一個用於訂購蛋糕的多屏應用。這會用到幾個表單,而表單對你來說已經不新鮮了。但是,你還將學到如何讓類在它具有 @Published
屬性時遵循 Codable
,如何通過網路傳送和接收定單資料,以及如何驗證表單,等等。
隨著我們持續深入 Codable
,我希望你會繼續對它的靈活性和安全性印象深刻。特別是,我希望你記住它和較老的 UserDefaults
API有很大的不同 —— 不用精確地輸入字串這一點真是太好了!
言歸正傳,我們有很多工作要做,讓我們開始吧:使用 Single View App 模板建立一個新的 iOS 應用,取名為 CupcakeCorner。
與往常一樣,我們從這個專案會用到的新技術開始。
譯自Adding Codable conformance for @Published properties
為 @Published 屬性新增 Codable 實現
如果一個型別的所有屬性都已經遵循Codable
,那麼型別本身就可以遵循Codable
,無需進行額外的工作 —— Swift 會根據需要合成用於歸檔和解檔你的型別的程式碼。但是,當我們使用@Published
之類的屬性包裝器時,這就不管用了,意味著遵循Codable
需要我們做一些額外的工作。
class User: ObservableObject, Codable {
var name = "Paul Hudson"
}複製程式碼
上面的程式碼不會有編譯問題,因為String
本來就遵循Codable
。但是,如果我們把name
標記為@Published
,那麼程式碼將通不過編譯:
class User: ObservableObject, Codable {
@Published var name = "Paul Hudson"
}複製程式碼
@Published
屬性包裝器並不是魔術,“屬性包裝器”這個名稱來源於一個事實,我們的name
屬性被自動地包裝在另一種型別中,這個型別增加了一些附加的功能。對於@Published
,這是一個名叫Published
的結構體,可以儲存任何型別的值。
之前,我們研究瞭如何編寫適用於任何型別的泛型方法,而Published
結構體比那更進一步:整個型別本身就是泛型,這意味著你無法只通過Published
本身建立一個例項,而只能通過像建立一個包含字串的 published 物件這樣的方式來建立Published
例項。
如果這聽起來令人困惑,請做好筆記:這實際上是一個相當基礎的 Swift 原理,而且你已經使用了一段時間。思考一下 —— 我們不能說var names:Set
,可以嗎?Swift 不允許這樣做;Swift 要知道集合中有什麼。這是因為Set
是一個泛型:你必須建立一個Set
的例項。陣列和字典也是如此:我們總是使它們的內部具有特定的內容。
Swift 已經制定了規則,如果陣列包含Codable
型別,那麼整個陣列就是Codable
的,字典和集合也是如此。但是,SwiftUI 並沒有為Published
結構體提供相同的功能 —— 它沒有規定說“如果 published 物件是Codable
的,那麼Published
結構體本身也是Codable
的”。
因此,我們需要自己實現這些型別(遵循Codable
):我們需要告訴 Swift 應該載入和儲存哪些屬性,以及如何執行這兩項操作。
這些步驟都不是很難的,所以讓我們開始第一個步驟:告訴 Swift 應該載入和儲存哪些屬性。這是通過用一個列舉實現特殊協議CodingKey
來完成的,列舉中的每種情況都是我們要載入和儲存的屬性的名稱。簡單起見,這個列舉通常就叫CodingKeys
,在末尾帶有 s ,但如果你想給它起別的名字也沒問題。
因此,我們的第一步是建立一個遵循 `CodingKey` 的 `CodingKeys` 列舉,列出我們要歸檔和解檔的所有屬性。現在,把下面的程式碼新增到 `User` 類中:
enum CodingKeys: CodingKey {
case name
}複製程式碼
下一個任務是建立一個自定義的構造器,這個構造器會被賦予某種容器,我們並用這種容器來讀取所有屬性的值。這會涉及一些新的知識,不如讓我們先看一下程式碼 —— 把這個構造器新增到User
:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
}複製程式碼
即使程式碼不多,也至少有四樣新東西。
首先,這個構造器被傳入了一個新的型別,叫 Decoder
。它包含了我們所有的資料,而我們需要弄清楚如何讀取它們。
其次,任何繼承我們的User
類的人都必須用一個自定義實現來重寫這個構造器,以確保他們新增自己的值。我們用required
關鍵字標記構造器:required init
。有一種替代方法是將這個類標記為final
,以便不允許子類化,在這種情況下,我們要寫成final class User
,然後完全刪除required
關鍵字。
再次,在方法內部,我們通過decoder.container(keyedBy: CodingKeys.self)
向Decoder
例項請求一個跟我們在CodingKey
結構體中設定的所有編碼鍵匹配的容器。它的意思是 “這份資料應該具有一個容器,其中的鍵與我們在CodingKeys
列舉中列出的鍵相匹配” 。這是一個會丟擲錯誤的呼叫,因為這些鍵可能不存在。
最後,我們可以通過引用列舉中的 case 直接從容器中讀取值:container.decode(String.self, forKey: .name)
。這裡通過兩種方式提供了非常強大的安全性:我們明確表示希望讀取的是字串,因此,如果把name
改為整數,那麼程式碼會停止編譯;並且,我們是使用CodingKeys
列舉中的 case 而不是字串,因此沒有拼錯字串的風險。
為了讓User
類實現Codable
,我們還需要完成另一項任務:我們已經建立了一個構造器,以便 Swift 可以將資料解碼為User
,但是現在我們需要告訴 Swift 如何編碼User
—— 如何將其歸檔以便寫入 JSON 。
這個步驟跟剛剛編寫的構造器的過程幾乎相反:我們被傳入一個用於寫入的Encoder
例項,我們管它要一個以CodingKeys
列舉作為鍵的容器,然後依照鍵把每個值寫入容器。
這個步驟跟剛剛編寫的構造器的過程幾乎相反:我們被傳入一個用於寫入的Encoder
例項,我們管它要一個以CodingKeys
列舉作為鍵的容器,然後依照鍵把每個值寫入容器。
現在,把這個方法新增到User
類:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
}複製程式碼
現在,我們的程式碼可以編譯通過了:Swift 知道我們要寫入的資料,知道如何將某些編碼的資料轉換為物件的屬性,還知道如何將物件的屬性轉換為某些編碼的資料。
希望你能在這裡面看到相對 UserDefaults
的 “stringly 型別的” API 的真正優勢 —— 由於我們不使用字串,用 Codable
的話,想要出錯要難得多,因為它會自動檢查我們的資料型別是否正確。
我的公眾號 這裡有Swift及計算機程式設計的相關文章,以及優秀國外文章翻譯,歡迎關注~
???????????????????????