利用 PropertyListDecoder 解析 plist
開發 iOS App 時,我們有時會建立 plist 檔儲存 App 需要存取的固定資料,比方開發訂飲料 App 時,將飲料的 menu 存在 plist 檔,然後再利用 Swift 程式讀取,顯示在表格或 picker 上。
不過要如何將 plist 檔的內容變成我們自訂的型別呢 ? 透過 PropertyListDecoder 的幫忙,只要幾行程式就能搞定,接下來就讓我們親手實驗看看吧。
建立儲存可不可熟成紅茶飲料 menu 的 plist
File > New > File。
選擇 iOS Resources 下的 Property List。
將 plist 取名為 Drinks。
在 plist 裡輸入飲料的 menu
預設的 plist 如下圖所示,第一層 Root 的型別是 Dictionary。
plist 內容的第一層可以是 Array 或 Dictionary,由於我們想用 array 儲存 menu 裡的各種飲料,因此我們將 Root 的 Type 改成 Array。
接著我們點選 Root 的 +,新增 array 裡的第一筆飲料資料。
我們希望飲料的資料包含名字和價錢,因此我們將它的 Type 從 String 改成可以容納多個欄位的 Dictionary。
點選 Item 0 旁的三角形,讓它變成向下的三角形再點選 +,因為這樣才代表我們要新增 Dictionary 的欄位,而不是新增 array 裡的資料
在 Item 0 輸入熟成紅茶的資訊。
有了第一個飲料,第二個第三個就簡單多了,因為可以使用我們最愛的複製貼上。請先點選 Item 0 旁的三角形,讓它變成向右的三角形。
然後按下 cmd +c & cmd + v 產生 Item 1,此時 Item 1 也會是 Dictionary,而且已經包含欄位 name & price,我們只要修改內容即可。
以同樣的方法複製貼上,輸入 Item 2 的飲料。
可不可熟成紅茶還有很多飲料,不過彼得潘累了,我們就先用三種飲料測試吧。
定義 plist 內容對應的 Decodable 型別 Drink
為了將 plist 的內容變成自訂的型別,我們必須定義符合 plist 內容格式且遵從 protocol Decodable 的型別。定義此型別的原理跟定義 JSON 對應的資料型別類似,差別只在 plist & JSON 有著不同的格式,plist 背後的格式是 XML。
plist 裡的 Dictionary 型別將被定義成對應的自訂型別,因此我們定義遵從 protocol Decodable 的 Drink 型別。
struct Drink: Decodable {
}
Dictionary 的 key 將成為自訂型別 property 的名字,property 的型別則由 Dictionary value 的 Type 決定。如下圖所示,Plist 裡資料的型別有七種,這七種都可以對應到常見的 Swift 基本型別。
因此我們在 Drink 裡宣告型別為 String & Int 的 property name & price。
struct Drink: Decodable {
let name: String
let price: Int
}
利用 PropertyListDecoder 析解 plist
let url = Bundle.main.url(forResource: "Drinks", withExtension: "plist")!
if let data = try? Data(contentsOf: url),
let drinks = try? PropertyListDecoder().decode([Drink].self, from: data) {
print(drinks)
}
說明
let url = Bundle.main.url(forResource: "Drinks", withExtension: "plist")!
取得 Drinks.plist 檔的 URL。
if let data = try? Data(contentsOf: url),
let drinks = try? PropertyListDecoder().decode([Drink].self, from: data) {
print(drinks)
}
從 URL 讀取 Data 後,利用 PropertyListDecoder 的 decode function 將 Data 變成型別 [Drink] 的 array。decode function 的宣告如下,第一個參數 type 控制轉換後的資料型別,因此我們傳入 [Drink].self
將得到型別 [Drink] 的飲料清單。
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable