Swift 4中的Encodable和Decodable

在iOS的日常開發(fā)中碾牌,許多任務涉及調用API和Web服務淮逊,將數(shù)據(jù)保存到磁盤以及使用自定義類型對代表我們應用程序用例和功能的對象進行建模庄新。這樣做時偿短,我們必須將數(shù)據(jù)與中間格式(JSON广恢,屬性列表)之間進行轉換凯旋。對于數(shù)據(jù)編碼和解碼任務,Swift提供了可編碼和可解碼協(xié)議钉迷。通過確認這些協(xié)議至非,可以將自定義類型編碼到外部表示形式(例如JSON和Property List(pList))并從中解碼。在本文中糠聪,我將介紹如何使用Encodable和Decodable在JSON和JSON之間轉換自定義類型實例荒椭,以及使用這兩種協(xié)議進行編碼和解碼任務的許多方面。

Encoding

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

Decoding

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

Codable

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

typealias Codable = Encodable & Decodable

自動編碼和解碼

默認情況下界赔,Swift Standard Library和Foundation Framework中的許多類型(如Int丢习,String,Data淮悼,URL咐低,Date等)都是可編碼的。為了使任何自定義類型都自動成為可編碼的袜腥,它應符合可編碼協(xié)議见擦,并且其所有存儲的屬性都應是可編碼的。

例如羹令,這是一個表示Movie的結構鲤屡。

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

只需遵循Codable,即可對Movie類型進行編碼和解碼福侈。

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

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

例如堰汉,假設我們有MovieDetail結構來表示電影細節(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

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

要以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ù)會拋出并且可能失敗,這就是為什么需要使用try的原因守伸。

讓我們打印movieData绎秒,

print(movieData)

在Xcode調試控制臺中,打印movieDate僅顯示原始字節(jié)數(shù)尼摹,我們將其轉換為可讀的JSON字符串见芹。

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

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

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

使用Coding Keys

可編碼類型定義符合CodingKey協(xié)議的嵌套枚舉CodingKeys,其情況定義編碼或解碼時必須包括的屬性和二。屬性的名稱應與自定義類型中相應屬性的名稱匹配徘铝。要在編碼表示形式或解碼類型中排除某些屬性,只需在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    
   }
}

手動實現(xiàn)Encoding和Decoding

如果自定義類型的結構與JSON的結構不同淹魄,則可以實現(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)

要使用以下結構將此電影對象編碼為JSON堡距,

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

在這里甲锡,我們可以在上面的JSON中看到電影詳細信息不像電影中那樣是嵌套結構,我們可以使用編碼功能將電影對象手動編碼為上面的表示形式吏颖。

encode(to: Encoder)

讓我們使用CodingKeys更新Movie結構搔体,如下所示:

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)在恨樟,我們實現(xiàn)Encodable并描述encode函數(shù)內部的編碼邏輯半醉。

在這里,我將Movie結構的符合性更改為Encodable劝术,而不是Codable缩多,以僅演示編碼呆奕。如果只需要對自定義類型執(zhí)行編碼,則可以使用Encodable衬吆,僅解碼可以使用Decodable梁钾,如果需要對自定義類型執(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提供了對編碼器存儲空間的API姆泻,可通過key進行訪問。

現(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字符串轉換為Movie類型的實例,則可以通過實現(xiàn)以下必需的初始化程序來手動解碼孝凌。

init(from decoder: Decoder)

現(xiàn)在方咆,我們將一致性更改為Decodable,并描述init內部的解碼邏輯(來自解碼器: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)             
    }
}

處理錯誤

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

JSONDecoder拋出DecodingError,其中包含不同的錯誤情況穆碎,例如.dataCorrupted牙勘,.keyNotFound,.typeMismatch所禀,.valueNotFound方面。類似地,JSONEncoder會引發(fā)EncodingError以及編碼期間可能出現(xiàn)的各種錯誤情況色徘。

總結

可編碼和可解碼是快速標準庫中的強大功能恭金,可輕松處理數(shù)據(jù)編碼和解碼任務」硬撸快速標準庫和Foundation框架中的許多類型(如日期横腿,URL)都是開箱即用的可編碼的,這使可編碼和可解碼的數(shù)據(jù)序列化/反序列化引人注目斤寂。此外耿焊,JSONDecoder和JSONEncoder提供正確和準確的錯誤處理,這是許多第三方JSON序列化/反序列化庫所缺少的遍搞。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末罗侯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溪猿,更是在濱河造成了極大的恐慌钩杰,老刑警劉巖纫塌,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異讲弄,居然都是意外死亡措左,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門避除,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怎披,“玉大人,你說我怎么就攤上這事瓶摆∏恚” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵赏壹,是天一觀的道長鱼炒。 經(jīng)常有香客問我,道長蝌借,這世上最難降的妖魔是什么昔瞧? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮菩佑,結果婚禮上自晰,老公的妹妹穿的比我還像新娘。我一直安慰自己稍坯,他們只是感情好酬荞,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞧哟,像睡著了一般混巧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勤揩,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天咧党,我揣著相機與錄音,去河邊找鬼陨亡。 笑死傍衡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的负蠕。 我是一名探鬼主播蛙埂,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遮糖!你這毒婦竟也來了绣的?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎被辑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敬惦,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡盼理,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俄删。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏怔。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖畴椰,靈堂內的尸體忽然破棺而出臊诊,到底是詐尸還是另有隱情,我是刑警寧澤斜脂,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布抓艳,位于F島的核電站,受9級特大地震影響帚戳,放射性物質發(fā)生泄漏玷或。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一片任、第九天 我趴在偏房一處隱蔽的房頂上張望偏友。 院中可真熱鬧,春花似錦对供、人聲如沸位他。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹅髓。三九已至,卻和暖如春京景,著一層夾襖步出監(jiān)牢的瞬間迈勋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工醋粟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留靡菇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓米愿,卻偏偏與公主長得像厦凤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子育苟,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354