在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