在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