Swift基礎(chǔ)知識(shí)相關(guān)(二) —— 編碼和解碼(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2019.07.22 星期一

前言

這個(gè)專題我們就一起看一下Swfit相關(guān)的基礎(chǔ)知識(shí)泽疆。感興趣的可以看上面幾篇容握。
1. Swift基礎(chǔ)知識(shí)相關(guān)(一) —— 泛型(一)

開(kāi)始

首先看下主要內(nèi)容

主要內(nèi)容:在本教程中,您將學(xué)習(xí)Swift中的所有編碼和解碼馍迄,探索自定義日期和自定義編碼等基礎(chǔ)知識(shí)和高級(jí)主題尿庐。

然后看些寫(xiě)作環(huán)境

Swift 5, iOS 12, Xcode 10

iOS應(yīng)用程序的一項(xiàng)常見(jiàn)任務(wù)是保存數(shù)據(jù)并通過(guò)網(wǎng)絡(luò)發(fā)送數(shù)據(jù)玩祟。 但在此之前靡馁,您需要通過(guò)稱為編碼或序列化(encoding or serialization)的過(guò)程將數(shù)據(jù)轉(zhuǎn)換為合適的格式欲鹏。

在應(yīng)用中使用之前,您還需要將通過(guò)網(wǎng)絡(luò)發(fā)送的已保存數(shù)據(jù)轉(zhuǎn)換為合適的格式臭墨。 該反向過(guò)程稱為解碼或反序列化(decoding or deserialization)赔嚎。

在本教程中,您將通過(guò)管理自己的toy store了解有關(guān)Swift編碼和解碼的所有信息胧弛。 您將在此過(guò)程中探索以下主題:

  • snake case and camel case之間切換尤误。
  • 定義自定義編碼key。
  • 使用keyed, unkeyed and nested containers结缚。
  • 處理嵌套類(lèi)型袄膏,日期,子類(lèi)和多態(tài)類(lèi)型掺冠。

有很多東西可以了解,所以是時(shí)候開(kāi)始了码党!

注意:本教程假定您具有JSON的基本知識(shí)德崭。 如果您需要快速瀏覽,請(qǐng)查看此 cheat sheet揖盘。

打開(kāi)起始項(xiàng)目眉厨,通過(guò)轉(zhuǎn)到View ? Navigators ? Show Project Navigator,確保在Xcode中可以看到Project navigator兽狭。 打開(kāi)Nested types憾股。

ToyEmployee添加Codable遵守:

struct Toy: Codable {
  ...
}
struct Employee: Codable {
  ...
}

Codable本身不是協(xié)議,而是另外兩個(gè)協(xié)議的別名:EncodableDecodable箕慧。 正如您可能猜到的那樣服球,這兩個(gè)協(xié)議聲明類(lèi)型可以編碼為不同的格式并從其中解碼。

您不需要再做任何事情颠焦,因?yàn)?code>Toy和Employee的所有存儲(chǔ)屬性(stored properties)都是可編碼(codable)的斩熊。 默認(rèn)情況下,Swift標(biāo)準(zhǔn)庫(kù)和基礎(chǔ)類(lèi)型(Swift Standard Library and Foundation )中的許多基本類(lèi)型(例如伐庭,StringURL)都是可編碼的粉渠。

注意:您可以將可編碼類(lèi)型編碼為各種格式分冈,例如Property Lists (PLists)XMLJSON霸株,但是對(duì)于本教程雕沉,您只能使用JSON。

添加JSONEncoderJSONDecoder來(lái)處理toysemployeesJSON編碼和解碼:

let encoder = JSONEncoder()
let decoder = JSONDecoder()

這就是使用JSON所需的全部?jī)?nèi)容去件。 第一次編碼和解碼挑戰(zhàn)的時(shí)間坡椒!


Encoding and Decoding Nested Types

Employee包含一個(gè)Toy屬性 - 它是一個(gè)嵌套類(lèi)型(nested type)。 編碼employee的JSON結(jié)構(gòu)與Employee結(jié)構(gòu)匹配:

{
  "name" : "John Appleseed",
  "id" : 7,
  "favoriteToy" : {
    "name" : "Teddy Bear"
  }
}
public struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy
}

JSONfavoriteToy中嵌套name箫攀,所有JSON鍵與EmployeeToy存儲(chǔ)屬性相同肠牲,因此您可以根據(jù)數(shù)據(jù)類(lèi)型層次結(jié)構(gòu)輕松理解JSON結(jié)構(gòu)。 如果您的屬性名稱與您的JSON字段名稱匹配靴跛,并且您的屬性都是Codable缀雳,那么您可以非常輕松地轉(zhuǎn)換為JSON或從JSON轉(zhuǎn)換。 你現(xiàn)在就試試梢睛。

Gifts部門(mén)為員工提供他們喜歡的玩具作為生日禮物肥印。 添加以下代碼以將員工的數(shù)據(jù)發(fā)送到禮品部門(mén):

// 1
let data = try encoder.encode(employee)
// 2
let string = String(data: data, encoding: .utf8)!

以下是此代碼的工作原理:

  • 1) 使用encode(_:)employee編碼為JSON(我告訴過(guò)你這很簡(jiǎn)單!)绝葡。
  • 2) 從編碼data創(chuàng)建一個(gè)字符串以使其可視化深碱。

注意:按Shift-Return可將playground運(yùn)行到當(dāng)前行,或單擊藍(lán)色play按鈕藏畅。 要查看結(jié)果敷硅,可以將值打印print到調(diào)試器控制臺(tái),或單擊結(jié)果側(cè)欄中的Show Result按鈕愉阎。

編碼過(guò)程生成有效數(shù)據(jù)绞蹦,因此禮品部門(mén)可以重新創(chuàng)建員工:

let sameEmployee = try decoder.decode(Employee.self, from: data)

在這里,您已經(jīng)使用decode(_:from :)data解碼回Employee ......您已經(jīng)讓您的員工非常開(kāi)心榜旦。 按藍(lán)色play按鈕以運(yùn)行Playground并查看結(jié)果幽七。

是時(shí)候進(jìn)行下一次挑戰(zhàn)!


Switching Between Snake Case and Camel Case Formats

禮品部門(mén)API已經(jīng)從camel caselooksLikeThis)轉(zhuǎn)換到snake caselooks_like_this_instead)以格式化其JSON的鍵溅呢。

但是EmployeeToy的所有存儲(chǔ)屬性都只使用camel case的情況澡屡! 幸運(yùn)的是,Foundation為您提供服務(wù)咐旧。

打開(kāi)Snake case vs camel case并在創(chuàng)建編碼器和解碼器之后添加以下代碼驶鹉,然后再使用它們:

encoder.keyEncodingStrategy = .convertToSnakeCase
decoder.keyDecodingStrategy = .convertFromSnakeCase

在這里,您將keyEncodingStrategy設(shè)置為.convertToSnakeCase以對(duì)employee進(jìn)行編碼铣墨。 您還將keyDecodingStrategy設(shè)置為.convertFromSnakeCase以解碼snakeData梁厉。

運(yùn)行playground并檢查snakeString。 在這種情況下,編碼的employee看起來(lái)像這樣(雙關(guān)語(yǔ)):

{
  "name" : "John Appleseed",
  "id" : 7,
  "favorite_toy" : {
    "name" : "Teddy Bear"
  }
} 

JSON中的格式現(xiàn)在是favorite_toy词顾,并且您已將其轉(zhuǎn)換回Employee結(jié)構(gòu)中的favoriteToy八秃。 你再次保存了(員工的出生日!)


Working With Custom JSON Keys

禮品部門(mén)再次更改其API以使用與您的EmployeeToy存儲(chǔ)屬性不同的JSON key

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : {
    "name" : "Teddy Bear"
  }
}

現(xiàn)在肉盹,APIgift取代了favoriteToy昔驱。

這意味著JSON中的字段名稱將不再與您的類(lèi)型中的屬性名稱匹配。 您可以定義自定義編碼鍵(custom coding keys )以提供屬性的編碼名稱上忍。 您可以通過(guò)向類(lèi)型添加特殊枚舉來(lái)完成此操作骤肛。 打開(kāi)custom coding keys并在Employee類(lèi)型中添加此代碼:

enum CodingKeys: String, CodingKey {
  case name, id, favoriteToy = "gift"
}

CodingKeys是上面提到的特殊枚舉。 它符合CodingKey并具有String原始值窍蓝。 這里是您將favoriteToy映射到gift的地方腋颠。

如果此枚舉存在,則只有此處出現(xiàn)的情況將用于編碼和解碼吓笙,因此即使您的屬性不需要映射淑玫,它也必須包含在枚舉中,如nameid在此處所示面睛。

運(yùn)行playground并查看編碼的字符串值 - 您將看到正在使用的新字段名稱絮蒿。 由于自定義編碼密鑰custom coding keys,JSON不再依賴于您存儲(chǔ)的屬性叁鉴。

是時(shí)候進(jìn)行下一次挑戰(zhàn)土涝!


Working With Flat JSON Hierarchies

現(xiàn)在,Gifts部門(mén)的API不希望其JSON中有任何嵌套類(lèi)型幌墓,因此它們的代碼如下所示:

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : "Teddy Bear"
}

這與您的模型結(jié)構(gòu)不匹配但壮,因此您需要編寫(xiě)自己的編碼邏輯并描述如何編碼每個(gè)EmployeeToy存儲(chǔ)的屬性。

首先常侣,打開(kāi)Keyed containers蜡饵。 您將看到一個(gè)聲明為EncodableEmployee類(lèi)型。 它也在擴(kuò)展中聲明為Decodable袭祟。 這種拆分是為了保持你使用Swift結(jié)構(gòu)體獲得的free member-wise初始化程序。 如果在主定義中聲明了init方法捞附,則會(huì)丟失該方法巾乳。 在Employee中添加此代碼:

// 1
enum CodingKeys: CodingKey {
  case name, id, gift
}

func encode(to encoder: Encoder) throws {
  // 2
  var container = encoder.container(keyedBy: CodingKeys.self)
  // 3  
  try container.encode(name, forKey: .name)
  try container.encode(id, forKey: .id)
  // 4
  try container.encode(toy.name, forKey: .gift)
}

對(duì)于您在上面看到的簡(jiǎn)單情況,編譯器會(huì)自動(dòng)為您實(shí)現(xiàn)encode(to :)鸟召。 現(xiàn)在胆绊,你自己做了。 這是代碼正在做的事情:

  • 1) 創(chuàng)建一組編碼鍵來(lái)表示您的JSON字段欧募。 因?yàn)槟鷽](méi)有進(jìn)行任何映射压状,所以您不需要將它們聲明為字符串,因?yàn)闆](méi)有原始值。
  • 2) 創(chuàng)建KeyedEncodingContainer种冬。 這就像您可以在編碼時(shí)存儲(chǔ)屬性的字典镣丑。
  • 3) 將nameid屬性直接編碼到容器中。
  • 4) 使用禮品密鑰將toy的名稱直接編碼到容器中

運(yùn)行playground并檢查編碼字符串的值 - 它將匹配本節(jié)頂部的JSON娱两。 能夠選擇對(duì)哪些鍵進(jìn)行編碼的屬性為您提供了很大的靈活性莺匠。

解碼過(guò)程與編碼過(guò)程相反。 用這個(gè)替換可怕的fatalError("To do")

// 1
let container = try decoder.container(keyedBy: CodingKeys.self)
// 2
name = try container.decode(String.self, forKey: .name)
id = try container.decode(Int.self, forKey: .id)
// 3
let gift = try container.decode(String.self, forKey: .gift)
favoriteToy = Toy(name: gift)

與編碼一樣十兢,對(duì)于簡(jiǎn)單的情況趣竣,編譯器會(huì)自動(dòng)為您生成init(from :),但是您自己就是這樣做的旱物。 這是代碼正在做的事情:

  • 1) 從解碼器獲取一個(gè)鍵控容器遥缕,它將包含JSON中的所有屬性。
  • 2) 使用適當(dāng)?shù)念?lèi)型和編碼key從容器中提取name and id宵呛。
  • 3) 提取禮物的名稱单匣,并使用它來(lái)構(gòu)建Toy并將其分配給正確的屬性。

添加一行以從平面JSON重新創(chuàng)建employee

let sameEmployee = try decoder.decode(Employee.self, from: data)

這一次烤蜕,您選擇了哪些屬性來(lái)解碼哪些鍵封孙,并有機(jī)會(huì)在解碼過(guò)程中進(jìn)一步工作。 手動(dòng)編碼和解碼功能強(qiáng)大讽营,為您提供靈活性虎忌。 您將在接下來(lái)的挑戰(zhàn)中了解更多相關(guān)信息。


Working With Deep JSON Hierarchies

禮品部門(mén)希望確保員工的生日禮物只能是玩具橱鹏,因此其API會(huì)生成如下所示的JSON:

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : {
    "toy" : {
      "name" : "Teddy Bear"
    }
  }
}

你在toygift里面的toy同時(shí)嵌入name膜蠢。 與Employee層次結(jié)構(gòu)相比,JSON結(jié)構(gòu)添加了額外級(jí)別的縮進(jìn)莉兰,因此在這種情況下您需要使用嵌套的鍵控容器(nested keyed containers)作為禮物挑围。

打開(kāi)嵌套的鍵控容器并將以下代碼添加到Employee

// 1  
enum CodingKeys: CodingKey {  
  case name, id, gift
}
// 2
enum GiftKeys: CodingKey {
  case toy
}
// 3
func encode(to encoder: Encoder) throws {
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(name, forKey: .name)
  try container.encode(id, forKey: .id)
  // 4  
  var giftContainer = container
    .nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
  try giftContainer.encode(toy, forKey: .toy)
}

這就是上面代碼的工作原理:

  • 1) 創(chuàng)建top-level coding keys
  • 2) 創(chuàng)建另一組編碼鍵糖荒,您將使用它來(lái)創(chuàng)建另一個(gè)容器杉辙。
  • 3) 按照您習(xí)慣的方式對(duì)name and id進(jìn)行編碼。
  • 4) 創(chuàng)建一個(gè)嵌套容器nestedContainer(keyedBy:forKey :)并用它編碼toy捶朵。

運(yùn)行playground并檢查編碼的字符串以查看多級(jí)JSON蜘矢。 您可以使用盡可能多的嵌套容器,因?yàn)槟腏SON具有縮進(jìn)級(jí)別综看。 在現(xiàn)實(shí)世界的API中使用復(fù)雜而深入的JSON層次結(jié)構(gòu)時(shí)品腹,這很方便。

在這種情況下红碑,解碼很簡(jiǎn)單舞吭。 添加以下擴(kuò)展名:

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    id = try container.decode(Int.self, forKey: .id)
    let giftContainer = try container
      .nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
    favoriteToy = try giftContainer.decode(Toy.self, forKey: .toy)
  }
}

let sameEmployee = try decoder.decode(Employee.self, from: nestedData)

您已使用嵌套解碼容器(nested decoding container)nestedData解碼為Employee


Encoding and Decoding Dates

禮品部門(mén)需要知道員工的生日才能發(fā)送禮物,因此他們的JSON看起來(lái)像這樣:

{
  "id" : 7,
  "name" : "John Appleseed",
  "birthday" : "29-05-2019",
  "toy" : {
    "name" : "Teddy Bear"
  }
}

日期沒(méi)有JSON標(biāo)準(zhǔn)羡鸥,這對(duì)于每個(gè)與之合作過(guò)的程序員而言都是如此蔑穴。 JSONEncoderJSONDecoder默認(rèn)使用日期的timeIntervalSinceReferenceDate的雙重表示,這在并不常見(jiàn)兄春。

您需要使用日期策略(date strategy)澎剥。 在try encoder.encode(employee)語(yǔ)句之前,將此代碼塊添加到日期(Dates)

// 1
extension DateFormatter {
  static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "dd-MM-yyyy"
    return formatter
  }()
}
// 2
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
decoder.dateDecodingStrategy = .formatted(.dateFormatter)

這是代碼的作用:

  • 1) 創(chuàng)建與所需格式匹配的日期格式化程序赶舆。 它作為DateFormatter的靜態(tài)屬性添加哑姚,因?yàn)檫@是您的代碼的良好實(shí)踐,因此格式化程序是可重用的芜茵。
  • 2) 將dateEncodingStrategydateDecodingStrategy設(shè)置為.formatted(.dateFormatter)叙量,告訴編碼器和解碼器在編碼和解碼日期時(shí)使用格式化程序。

檢查dateString并檢查日期格式是否正確九串。 您已確保禮品部門(mén)將按時(shí)交付禮品 - 即將推出绞佩!

還有一些挑戰(zhàn),你已經(jīng)完成了猪钮。


Encoding and Decoding Subclasses

Gifts部門(mén)API可以根據(jù)類(lèi)層次結(jié)構(gòu)(class hierarchies)處理JSON:

{
  "toy" : {
    "name" : "Teddy Bear"
  },
  "employee" : {
    "name" : "John Appleseed",
    "id" : 7
  },
  "birthday" : 580794178.33482599
}

employee匹配沒(méi)有toy or birthday的基類(lèi)結(jié)構(gòu)品山。 打開(kāi)Subclasses并使BasicEmployee符合Codable

class BasicEmployee: Codable {

這會(huì)給你一個(gè)錯(cuò)誤,因?yàn)?code>GiftEmployee還不是Codable烤低。 通過(guò)向GiftEmployee添加以下內(nèi)容來(lái)糾正:

// 1              
enum CodingKeys: CodingKey {
  case employee, birthday, toy
}  
// 2
required init(from decoder: Decoder) throws {
  let container = try decoder.container(keyedBy: CodingKeys.self)
  birthday = try container.decode(Date.self, forKey: .birthday)
  toy = try container.decode(Toy.self, forKey: .toy)
  // 3
  let baseDecoder = try container.superDecoder(forKey: .employee)
  try super.init(from: baseDecoder)
}  

此代碼涵蓋解碼:

  • 1) 添加相關(guān)的編碼密鑰肘交。
  • 2) 解碼特定于子類(lèi)的屬性。
  • 3) 使用superDecoder(forKey :)獲取一個(gè)適合傳遞給超類(lèi)的init(from :)方法的解碼器實(shí)例扑馁,然后初始化超類(lèi)涯呻。

現(xiàn)在在GiftEmployee中實(shí)現(xiàn)編碼:

override func encode(to encoder: Encoder) throws {
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(birthday, forKey: .birthday)
  try container.encode(toy, forKey: .toy)
  let baseEncoder = container.superEncoder(forKey: .employee)
  try super.encode(to: baseEncoder)
}

它是相同的模式,但您使用superEncoder(forKey :)為超類(lèi)準(zhǔn)備編碼器腻要。 將以下代碼添加到playground的末尾以測(cè)試您的可編碼子類(lèi):

let giftEmployee = GiftEmployee(name: "John Appleseed", id: 7, birthday: Date(), 
                                toy: toy)
let giftData = try encoder.encode(giftEmployee)
let giftString = String(data: giftData, encoding: .utf8)!
let sameGiftEmployee = try decoder.decode(GiftEmployee.self, from: giftData)

檢查giftString的值以查看您的工作复罐! 您可以在應(yīng)用程序中處理更復(fù)雜的類(lèi)層次結(jié)構(gòu)。 是時(shí)候進(jìn)行下一次挑戰(zhàn)雄家!


Handling Arrays With Mixed Types

Gifts部門(mén)API公開(kāi)了適用于不同類(lèi)型員工的JSON:

[
  {
    "name" : "John Appleseed",
    "id" : 7
  },
  {
    "id" : 7,
    "name" : "John Appleseed",
    "birthday" : 580797832.94787002,
    "toy" : {
      "name" : "Teddy Bear"
    }
  }
]

此JSON數(shù)組是多態(tài)的效诅,因?yàn)樗J(rèn)和自定義employees。 打開(kāi)Polymorphic types趟济,您將看到不同類(lèi)型的員工由枚舉表示乱投。 首先,聲明枚舉是Encodable

enum AnyEmployee: Encodable {

然后將此代碼添加到枚舉的主體:

  // 1
enum CodingKeys: CodingKey {
  case name, id, birthday, toy
}  
// 2
func encode(to encoder: Encoder) throws {
  var container = encoder.container(keyedBy: CodingKeys.self)
  
  switch self {
    case .defaultEmployee(let name, let id):
      try container.encode(name, forKey: .name)
      try container.encode(id, forKey: .id)
    case .customEmployee(let name, let id, let birthday, let toy):  
      try container.encode(name, forKey: .name)
      try container.encode(id, forKey: .id)
      try container.encode(birthday, forKey: .birthday)
      try container.encode(toy, forKey: .toy)
    case .noEmployee:
      let context = EncodingError.Context(codingPath: encoder.codingPath, 
                                          debugDescription: "Invalid employee!")
      throw EncodingError.invalidValue(self, context)
  }
}

以下是此代碼的用途:

  • 1) 定義足夠的編碼密鑰以涵蓋所有可能的情況咙好。
  • 2) 對(duì)有效員工進(jìn)行編碼篡腌,并對(duì)無(wú)效員工拋出EncodingError.invalidValue(_:_ :)褐荷。

通過(guò)將以下內(nèi)容添加到playground的末尾來(lái)測(cè)試您的編碼:

let employees = [AnyEmployee.defaultEmployee("John Appleseed", 7), 
                 AnyEmployee.customEmployee("John Appleseed", 7, Date(), toy)]
let employeesData = try encoder.encode(employees)
let employeesString = String(data: employeesData, encoding: .utf8)!

檢查employeesString的值以查看混合數(shù)組勾效。

解碼有點(diǎn)復(fù)雜,因?yàn)樵跊Q定如何繼續(xù)之前,你必須弄清楚JSON中的內(nèi)容层宫。 將以下代碼添加到playground

extension AnyEmployee: Decodable {
  init(from decoder: Decoder) throws {
    // 1
    let container = try decoder.container(keyedBy: CodingKeys.self) 
    let containerKeys = Set(container.allKeys)
    let defaultKeys = Set<CodingKeys>([.name, .id])
    let customKeys = Set<CodingKeys>([.name, .id, .birthday, .toy])
   
    // 2
   switch containerKeys {
      case defaultKeys:
        let name = try container.decode(String.self, forKey: .name)
        let id = try container.decode(Int.self, forKey: .id)
        self = .defaultEmployee(name, id)
      case customKeys:
        let name = try container.decode(String.self, forKey: .name)
        let id = try container.decode(Int.self, forKey: .id)
        let birthday = try container.decode(Date.self, forKey: .birthday)
        let toy = try container.decode(Toy.self, forKey: .toy)
        self = .customEmployee(name, id, birthday, toy)
      default:
        self = .noEmployee
    }
  }
}
// 4
let sameEmployees = try decoder.decode([AnyEmployee].self, from: employeesData) 

這就是它的工作原理:

  • 1) 像往常一樣獲取一個(gè)鍵控容器杨伙,然后檢查allKeys屬性以確定JSON中存在哪些鍵。
  • 2) 檢查containerKeys是否與默認(rèn)員工或自定義員工所需的密鑰匹配萌腿,并提取相關(guān)屬性限匣;否則,建立一個(gè).noEmployee毁菱。 如果沒(méi)有合適的默認(rèn)值米死,您可以選擇在此處拋出錯(cuò)誤。
  • 3) 將employeesData解碼為[AnyEmployee]贮庞。

您可以根據(jù)具體類(lèi)型對(duì)employeesData中的每個(gè)employee進(jìn)行解碼峦筒,就像編碼一樣。

只留下兩個(gè)挑戰(zhàn) - 下一個(gè)挑戰(zhàn)窗慎!


Working With Arrays

禮品部門(mén)為員工的生日禮物添加標(biāo)簽物喷;他們的JSON看起來(lái)像這樣:

[
  "teddy bear",
  "TEDDY BEAR",
  "Teddy Bear"
]

JSON數(shù)組包含小寫(xiě),大寫(xiě)和常規(guī)標(biāo)簽名稱遮斥。 這次你不需要任何密鑰峦失,所以你使用一個(gè)unkeyed container

打開(kāi)Unkeyed container容器并將編碼代碼添加到Label

func encode(to encoder: Encoder) throws {
  var container = encoder.unkeyedContainer()
  try container.encode(toy.name.lowercased())
  try container.encode(toy.name.uppercased())
  try container.encode(toy.name)
}

UnkeyedEncodingContainer就像你目前使用的容器一樣工作术吗,除了......你猜對(duì)了尉辑,沒(méi)有鍵。 可以將其視為寫(xiě)入JSON數(shù)組而不是JSON字典藐翎。 您將三個(gè)不同的字符串編碼到容器中材蹬。

運(yùn)行playground并檢查labelString以查看您的數(shù)組。

這是解碼的外觀吝镣。 將以下代碼添加到playground的末尾:

extension Label: Decodable {
  // 1
  init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    var name = ""
    while !container.isAtEnd {
      name = try container.decode(String.self)
    }
    toy = Toy(name: name)
  }
}
// 2
let sameLabel = try decoder.decode(Label.self, from: labelData)

這就是上面代碼的工作原理:

  • 1) 獲取解碼器的無(wú)鍵解碼容器(unkeyed decoding container)堤器,并使用decode(_ :)循環(huán)解碼,以解碼最終的末贾,格式正確的標(biāo)簽名稱闸溃。
  • 2) 使用未加密碼的解碼容器(unkeyed decoding container)labelData解碼為Label

您遍歷整個(gè)解碼容器拱撵,因?yàn)樽詈髸?huì)出現(xiàn)正確的標(biāo)簽名稱辉川。

你最后一次挑戰(zhàn)的時(shí)間!


Working With Arrays Within Objects

禮品部門(mén)想要查看員工生日禮物的名稱和標(biāo)簽拴测,因此其API生成的JSON如下所示:

{
  "name" : "Teddy Bear",
  "label" : [
    "teddy bear",
    "TEDDY BEAR",
    "Teddy Bear"
  ]
}

您將標(biāo)簽名稱嵌套在label內(nèi)乓旗。 與前一個(gè)挑戰(zhàn)相比,JSON結(jié)構(gòu)增加了額外的縮進(jìn)級(jí)別集索,因此在這種情況下屿愚,您需要使用嵌套的無(wú)鍵容器(nested unkeyed containers)作為label汇跨。

打開(kāi)Nested unkeyed containers并將以下代碼添加到Toy

func encode(to encoder: Encoder) throws {
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(name, forKey: .name)
  var labelContainer = container.nestedUnkeyedContainer(forKey: .label)                   
  try labelContainer.encode(name.lowercased())
  try labelContainer.encode(name.uppercased())
  try labelContainer.encode(name)
}

在這里,您將創(chuàng)建一個(gè)嵌套的無(wú)鍵容器妆距,并使用三個(gè)標(biāo)簽值填充它穷遂。 運(yùn)行playground并檢查string以檢查結(jié)構(gòu)是否正確。

如果JSON具有更多縮進(jìn)級(jí)別娱据,則可以使用更多嵌套容器蚪黑。 將解碼代碼添加到playground頁(yè)面:

extension Toy: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    var labelContainer = try container.nestedUnkeyedContainer(forKey: .label)
    var labelName = ""
    while !labelContainer.isAtEnd {
      labelName = try labelContainer.decode(String.self)
    }
    label = labelName
  }
}
let sameToy = try decoder.decode(Toy.self, from: data)

這遵循與以前相同的模式,通過(guò)數(shù)組并使用最終值來(lái)設(shè)置label的值中剩,但是來(lái)自嵌套的未鍵控容器(nested unkeyed container)忌穿。

恭喜您完成所有挑戰(zhàn)!

Encoding and decoding in Swift like a pro!

后記

本篇主要講述了Swift編碼和解碼结啼,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伴网,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妆棒,更是在濱河造成了極大的恐慌澡腾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糕珊,死亡現(xiàn)場(chǎng)離奇詭異动分,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)红选,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)澜公,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人喇肋,你說(shuō)我怎么就攤上這事坟乾。” “怎么了蝶防?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵甚侣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我间学,道長(zhǎng)殷费,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任低葫,我火速辦了婚禮详羡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘿悬。我一直安慰自己实柠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布善涨。 她就那樣靜靜地躺著窒盐,像睡著了一般茶行。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上登钥,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音娶靡,去河邊找鬼牧牢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姿锭,可吹牛的內(nèi)容都是我干的塔鳍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呻此,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼轮纫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起焚鲜,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掌唾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后忿磅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體糯彬,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年葱她,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撩扒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吨些,死狀恐怖搓谆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豪墅,我是刑警寧澤泉手,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站偶器,受9級(jí)特大地震影響螃诅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜状囱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一术裸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亭枷,春花似錦袭艺、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘤睹。三九已至,卻和暖如春答倡,著一層夾襖步出監(jiān)牢的瞬間轰传,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工瘪撇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留获茬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓倔既,卻偏偏與公主長(zhǎng)得像恕曲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渤涌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容