Swift 4中蘋果引入了全新的編碼與解碼支持,開發者可以更加方便地將數據轉化為JSON或存入本地磁盤。這個功能的核心是Codable協議,其定義如下:
typealias Codable = Decodable & Encodable
public protocol Decodable {
public init(from decoder: Decoder) throws
}
public protocol Encodable {
public func encode(to encoder: Encoder) throws
}
編碼及解析
本文主要介紹JSON的編碼和解析,使用JSONEncoder用于編碼,使用JSONDecoder用于解析。
let data = try! JSONEncoder().encode([1: 3])
let dict = try! JSONDecoder().decode([Int: Int].self, from: data)
print(dict)
需要注意傳入的數據必須是JSON格式的,所以model本身也必須是遵循JSON的規范的。比如基本類型本身并不是Object/Array,是不可以編碼或解析的。經實測,key可以不嚴格遵循JSON規定的String類型。了解JSON
基本類型
Swift的Enum,Struct和Class都支持Codable,先看一個簡單的示例。
// Enum的原始值一定要是Codable,一般使用的String/Int都是可以的
enum Level: String, Codable {
case large
case medium
case small
}
struct Location: Codable {
let latitude: Double
let longitude: Double
}
// CustomDebugStringConvertible只是為了更好打印
class City: Codable, CustomDebugStringConvertible {
let name: String
let pop: UInt
let level: Level
let location: Location
var debugDescription: String {
return """
{
"name": \(name),
"pop": \(pop),
"level": \(level.rawValue),
"location": {
"latitude": \(location.latitude),
"longitude": \(location.longitude)
}
}
"""
}
}
let jsonData = """
{
"name": "Shanghai",
"pop": 21000000,
"level": "large",
"location": {
"latitude": 30.40,
"longitude": 120.51
}
}
""".data(using: .utf8)!
do {
let city = try JSONDecoder().decode(City.self, from: jsonData)
print("city:", city)
} catch {
print(error.localizedDescription)
}
上述例子中一次性展示了三種基本類型的基本用法,需要注意的是所有存儲屬性的類型都需遵循Codable才可以推斷,計算屬性不受此限制。如有存儲屬性不遵循Codable,需要自行實現本文開頭協議中的方法。另外,class繼承的父類也需遵循Codable才可以自動推斷,否則也需自行實現。
自定義Key
由于Codable的key是直接用屬性名匹配的,所以當key不匹配時需要我們自定義并實現協議方法。比如上述的的name字段變成了short_name。我們需要:
- 定義一個枚舉遵循CodingKey協議且原始值為String(定義的最佳位置為嵌套在相應的model內)
- 實現Decodable的協議方法
代碼如下:
let jsonData = """
{
"short_name": "Shanghai", // 這里的key與model不再吻合
"pop": 21000000,
"level": "large",
"location": {
"latitude": "30.40",
"longitude": 120.51
}
}
""".data(using: .utf8)!
class City: Codable, CustomDebugStringConvertible {
//...其余代碼與上例一致,這里不重復
enum CodingKeys: String, CodingKey {
case name = "short_name"
case pop
case level
case location
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
pop = try container.decode(UInt.self, forKey: .pop)
level = try container.decode(Level.self, forKey: .level)
location = try container.decode(Location.self, forKey: .location)
}
}
進階篇
第一式:善用嵌套
如果JSON格式比較奇怪,比如一個和時間相關的model分組,其key為日期。JSON格式如下:
{
"2016-10-01": [Objects],
"2017-09-30": [Objects],
...
}
我們可以使用[String: [Object]]解析,這樣就可以避免key不整齊的尷尬。
第二式:巧用泛型
如果模型封裝得比較好,其實大部分屬性是可以復用的,我們可以通過泛型(繼承也可以實現,只是層級比較復雜,且不支持struct)來實現模型的部分復用。
struct Resource<Attributes>: Codable where Attributes: Codable {
let name: String
let url: URL
let attributes: Attributes
}
struct ImageAttributes: Codable {
let size: CGSize
let format: String
}
Resource<ImageAttributes> // 可以添加更多的Attributes,實現了多態
第三式:剝離麻煩
上文中提到Codable要求存儲屬性都遵循Codable才可以自動推斷,如果只有極少屬性不支持Codable的話,實現協議方法就顯得十分麻煩。我們可以將部分屬性剝離到子類或父類中(此方法僅限于class)。如下例所示:
let jsonData = """
{
"name": "_name",
"length": 4,
"uncodableNumber": 6
}
""".data(using: .utf8)!
class CodablePart: Codable {
let name: String
let length: UInt
}
class WholeModel: CodablePart {
enum CodingKeys: String, CodingKey {
case uncodableNumber
}
let uncodableNumber: NSNumber
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let number = try container.decode(Int.self, forKey: .uncodableNumber)
uncodableNumber = NSNumber(value: number)
try super.init(from: decoder)
}
}
do {
let model = try JSONDecoder().decode(WholeModel.self, from: jsonData)
print(model.name)
print(model.length)
print(model.uncodableNumber)
} catch {
print(error.localizedDescription)
}
第四式:偷天換日
有時候JSON中的格式并非我們所要,比如String格式的浮點型數字,我們希望直接在轉model時轉換為Double類型。我們可以使用中間類轉化類型。如下:
struct StringToDoubleConverter: Codable { // 此類將獲取的String轉化為Double
let value: Double?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
value = Double(string)
}
}
這樣我們就可以將希望轉化為Double的String字段類型指定為StringToDoubleConverter。不過訪問時層級多了.value一層,且類型為可選型(因String轉Double本來就可能失?。?。
后記
Codable提供了簡潔的API,使Swift的編碼與解析煥然一新。本來打算在本文闡述JSONEncoder,JSONDecoder,NSKeyedArchiver,NSKeyedUnarchiver與Codable結合的用法。但鑒于長度問題,將另開一篇介紹。本文基本用法參照了Medium上的兩篇博文,進階用法來自于自己探索的心得。