Swift 4中的Encodable和Decodable

在iOS的日常開發(fā)中,許多任務(wù)涉及調(diào)用API和Web服務(wù),將數(shù)據(jù)保存到磁盤以及使用自定義類型對(duì)代表我們應(yīng)用程序用例和功能的對(duì)象進(jìn)行建模。這樣做時(shí),我們必須將數(shù)據(jù)與中間格式(JSON,屬性列表)之間進(jìn)行轉(zhuǎn)換。對(duì)于數(shù)據(jù)編碼和解碼任務(wù),Swift提供了可編碼和可解碼協(xié)議。通過確認(rèn)這些協(xié)議,可以將自定義類型編碼到外部表示形式(例如JSON和Property List(pList))并從中解碼。在本文中,我將介紹如何使用Encodable和Decodable在JSON和JSON之間轉(zhuǎn)換自定義類型實(shí)例,以及使用這兩種協(xié)議進(jìn)行編碼和解碼任務(wù)的許多方面。

Encoding

將自定義類型實(shí)例轉(zhuǎn)換為其他表示形式(例如JSON和pList)的過程稱為編碼或序列化。對(duì)于編碼,自定義類型符合Encodable協(xié)議。

Decoding

將諸如JSON或pList之類的表示形式的數(shù)據(jù)轉(zhuǎn)換為自定義類型的實(shí)例的過程稱為解碼或反序列化。對(duì)于解碼,自定義類型符合Decodable協(xié)議。

Codable

為了支持編碼和解碼,自定義類型可以符合Codable協(xié)議,而后者同時(shí)符合Encodable和Decodable。

typealias Codable = Encodable & Decodable

自動(dòng)編碼和解碼

默認(rèn)情況下,Swift Standard Library和Foundation Framework中的許多類型(如Int,String,Data,URL,Date等)都是可編碼的。為了使任何自定義類型都自動(dòng)成為可編碼的,它應(yīng)符合可編碼協(xié)議,并且其所有存儲(chǔ)的屬性都應(yīng)是可編碼的。

例如,這是一個(gè)表示Movie的結(jié)構(gòu)。

struct Movie {
   var movieId: Int?
   var name: String?
}

只需遵循Codable,即可對(duì)Movie類型進(jìn)行編碼和解碼。

struct Movie: Codable {
    var movieId: Int?
    var name: String?
}

同樣,具有自定義類型屬性的自定義類型是可編碼的,只要其所有屬性都是可編碼的即可。

例如,假設(shè)我們有MovieDetail結(jié)構(gòu)來表示電影細(xì)節(jié)??。

struct MovieDetail: Codable {
    var language: String?
    var genre: String?
    var releaseDate: String?
    var bannerImageUrl: String?
}
struct Movie: Codable {
    var movieId: Int?
    var name: String?
    var movieDetails: MovieDetail?
}

由于MovieDetail也符合Codable,因此Movie也可編碼。 Swift集合類型(例如Array,Dictionary和Optional)只要包含Codable類型,就變?yōu)镃odable。

JSONEncoder 和 JSONDecoder

假設(shè)您的自定義類型是Codable,則可以使用JSONEncoder將您的類型編碼為其他類型,例如Data,可以將其發(fā)送到服務(wù)器或保存到磁盤。

要以raw bytes(Data)編碼Movie

let bannerUrl = "https://example.com"
let movieDetails = MovieDetails(language: "English", genre: "Action", releaseDate: "18-05-2018", bannerImageUrl: bannerUrl)
let movie = Movie(movieId: 2, name: "Deadpool 2", movieDetails: movieDetails)
let jsonEncoder = JSONEncoder()
let movieData = try jsonEncoder.encode(movie)

注意:編碼函數(shù)會(huì)拋出并且可能失敗,這就是為什么需要使用try的原因。

讓我們打印movieData,

print(movieData)

在Xcode調(diào)試控制臺(tái)中,打印movieDate僅顯示原始字節(jié)數(shù),我們將其轉(zhuǎn)換為可讀的JSON字符串。

let jsonString = String(data: movieData, encoding: .utf8)
print(jsonString)

現(xiàn)在,要將這些數(shù)據(jù)轉(zhuǎn)換回Movie類型的實(shí)例,您需要使用JSONDecoder。

let jsonDecoder = JSONDecoder.init()
if let data = movieData {
    let movie = try jsonDecoder.decode(Movie.self, from: data)
}

使用Coding Keys

可編碼類型定義符合CodingKey協(xié)議的嵌套枚舉CodingKeys,其情況定義編碼或解碼時(shí)必須包括的屬性。屬性的名稱應(yīng)與自定義類型中相應(yīng)屬性的名稱匹配。要在編碼表示形式或解碼類型中排除某些屬性,只需在CodingKeys枚舉中省略它們。

如果您想要在編碼數(shù)據(jù)中使用與自定義類型不同的鍵名,或者您的自定義類型中的某些屬性名稱與JSON中的某些屬性名稱不同,則將CodingKeys枚舉的原始值類型定義為String并使用case提供原始值。

struct Movie: Codable {
   var movieId: Int?
   var name: String?
   var movieDetails: MovieDetail?

   enum CodingKeys: String, CodingKey {
      
       case movieId = "id"
       case name 
       case movieDetails    
   }
}

手動(dòng)實(shí)現(xiàn)Encoding和Decoding

如果自定義類型的結(jié)構(gòu)與JSON的結(jié)構(gòu)不同,則可以實(shí)現(xiàn)自己的編碼和解碼邏輯。

let movieDetails = MovieDetails(language: "English", genre: "Action", releaseDate: "18-05-2018", bannerImageUrl: bannerUrl)
let movie = Movie(movieId: 2, name: "Deadpool 2", movieDetails: movieDetails)

要使用以下結(jié)構(gòu)將此電影對(duì)象編碼為JSON,

{ "movieId": 2, "name": "Deadpool 2", "language": "English", "genre": "Action", "releaseDate": "18-05-2018", "bannerUrl": "https://www.imdb.com/title/tt5463162/mediaviewer/rm4291446016" }

在這里,我們可以在上面的JSON中看到電影詳細(xì)信息不像電影中那樣是嵌套結(jié)構(gòu),我們可以使用編碼功能將電影對(duì)象手動(dòng)編碼為上面的表示形式。

encode(to: Encoder)

讓我們使用CodingKeys更新Movie結(jié)構(gòu),如下所示:

struct Movie {
    var movieId: Int?
    var name: String?
    var movieDetails: MovieDetail?
    enum CodingKeys: String, CodingKey {
        
       case language
       case genre
       case releaseDate
       case bannerUrl 
    }
}

現(xiàn)在,我們實(shí)現(xiàn)Encodable并描述encode函數(shù)內(nèi)部的編碼邏輯。

在這里,我將Movie結(jié)構(gòu)的符合性更改為Encodable,而不是Codable,以僅演示編碼。如果只需要對(duì)自定義類型執(zhí)行編碼,則可以使用Encodable,僅解碼可以使用Decodable,如果需要對(duì)自定義類型執(zhí)行編碼和解碼,則可以使用Codable。

extension Movie: Encodable {
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(movieId, forKey: .movieId)
    try container.encode(name, forKey: .name)
    try container.encode(movieDetails.language, forKey: .language)
    try container.encode(movieDetails.genre, forKey: .genre)
    try container.encode(movieDetails.releaseDate, forKey: .releaseDate)
    try container.encode(movieDetails.bannerImageUrl, forKey:  .bannerUrl)
  }
}

此處的container提供了對(duì)編碼器存儲(chǔ)空間的API,可通過key進(jìn)行訪問。

現(xiàn)在,如果您編碼并打印JSON字符串,您將獲得

{ "movieId": 2, "name": "Deadpool 2", "language": "English", "genre": "Action", "releaseDate": "18-05-2018", "bannerUrl": "https://www.imdb.com/title/tt5463162/mediaviewer/rm4291446016" }

如果我們必須將此JSON字符串轉(zhuǎn)換為Movie類型的實(shí)例,則可以通過實(shí)現(xiàn)以下必需的初始化程序來手動(dòng)解碼。

init(from decoder: Decoder)

現(xiàn)在,我們將一致性更改為Decodable,并描述init內(nèi)部的解碼邏輯(來自解碼器:Decoder)。

extension Movie: Decodable {
  init(from decoder: Decoder) throws {
       
     var container = try decoder.container(keyedBy: CodingKeys.self)
     movieId = try container.decode(Int.self, forKey: .movieId)
  
     name = try container.decode(String.self, forKey: .name)
     let language = try container.decode(String.self, forKey: .language)
     let genre = try container.decode(String.self, forKey: .genre)
     let releaseDate = try container.decode(String.self, forKey: .releaseDate)
     let bannerUrl = try container.decode(String.self, forKey: .bannerUrl)
     movieDetails =  MovieDetail(language: language, genre: genre, releaseDate: releaseDate, bannerImageUrl: bannerUrl)             
    }
}

處理錯(cuò)誤

JSONDecoder和JSONEncoder對(duì)象都配備了適當(dāng)?shù)腻e(cuò)誤處理機(jī)制,并且在編碼和解碼失敗時(shí)會(huì)引發(fā)錯(cuò)誤,這些錯(cuò)誤為開發(fā)人員提供了明確的反饋,告知他們代碼中到底出了什么問題。

JSONDecoder拋出DecodingError,其中包含不同的錯(cuò)誤情況,例如.dataCorrupted,.keyNotFound,.typeMismatch,.valueNotFound。類似地,JSONEncoder會(huì)引發(fā)EncodingError以及編碼期間可能出現(xiàn)的各種錯(cuò)誤情況。

總結(jié)

可編碼和可解碼是快速標(biāo)準(zhǔn)庫中的強(qiáng)大功能,可輕松處理數(shù)據(jù)編碼和解碼任務(wù)??焖贅?biāo)準(zhǔn)庫和Foundation框架中的許多類型(如日期,URL)都是開箱即用的可編碼的,這使可編碼和可解碼的數(shù)據(jù)序列化/反序列化引人注目。此外,JSONDecoder和JSONEncoder提供正確和準(zhǔn)確的錯(cuò)誤處理,這是許多第三方JSON序列化/反序列化庫所缺少的。

參考鏈接
https://medium.com/@manojkarkie/encodable-and-decodable-in-swift-4-747328a7c7c5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容