Swift 在 JSON解析方面有個(gè)比較有名的第三方庫——SwiftyJSON,之前我也一直用的它。雖然用著還不錯(cuò),但是它主要是為了避免手動(dòng)解析 JSON 數(shù)據(jù)時(shí)大量的解包操作,降低解包不當(dāng)導(dǎo)致 crash 的風(fēng)險(xiǎn),感覺主要是注重安全性,易用性方面還是差了點(diǎn)。它支持下標(biāo)操作,但畢竟是以字符串為鍵取值,IDE 不能自動(dòng)補(bǔ)全,不僅麻煩還容易寫錯(cuò),而且用下標(biāo)取到的值是JSON類型,一般還需要再進(jìn)行類型轉(zhuǎn)換,終究沒有操作一個(gè) Model 來得方便。所以我一直想寫一個(gè) JSON-Model 的映射器,我想要的效果是這樣的:
- 定義一個(gè) Model :
class JSONModel {
var error = ""
var count = 0
var posts = []
}
- 發(fā)送網(wǎng)絡(luò)請(qǐng)求后取得數(shù)據(jù),然后直接轉(zhuǎn)換成 JSONModel :
let jsonModel = data => JSOMModel.self
就這點(diǎn)需求,換個(gè)動(dòng)態(tài)語言那根本不是事兒,哪怕在 C# 這樣的靜態(tài)語言中也能很簡(jiǎn)單地實(shí)現(xiàn),只要用到反射特性就行了。然而 Swift 的反射實(shí)在是太弱了,運(yùn)行期只能查看屬性卻不能給屬性賦值。這簡(jiǎn)直無解,我讀了一下 SwiftyJSON 的源碼希望能找點(diǎn)靈感,然后發(fā)現(xiàn) SwfityJSON 的流程是這樣的(以直接解析 NSData 數(shù)據(jù)為例):
以一個(gè) NSData 類型的數(shù)據(jù)作為構(gòu)造器參數(shù)實(shí)例化一個(gè) JSON(一個(gè) struct ),在構(gòu)造器中調(diào)用
NSJSONSerialization.JSONObjectWithData(...)方法,如果 data 能被反序列化成一個(gè)AnyObject類型的對(duì)象的話,就調(diào)用另一個(gè)構(gòu)造器,把這個(gè)對(duì)象賦值給實(shí)例屬性object,否則就給object賦一個(gè)NSNull()。object是一個(gè)計(jì)算屬性,在給它賦值時(shí),會(huì)對(duì)它的類型進(jìn)行判斷,然后把它的類型信息存儲(chǔ)到實(shí)例屬性type中(type是一個(gè)自定義的枚舉類型,這個(gè)枚舉類型基本對(duì)應(yīng)了 Swift 中的幾種基本類型),最后把object的值進(jìn)行類型轉(zhuǎn)化后賦值給JSON中的一個(gè)特定類型的私有屬性,譬如是數(shù)組的話就賦值給rawArray,是字符串的話就賦值給rawString,等等。在獲取
object時(shí)會(huì)先判斷實(shí)例屬性type,根據(jù)type的值返回對(duì)應(yīng)的 rawValue,譬如type == .String的話,就返回rawString。然后像array和arrayValue這樣的都是計(jì)算屬性,array的話會(huì)先去判斷type是不是.Array,是就返回rawArray,否則返回nil,而arrayValue不會(huì)返回nil,若類型不匹配則返回一個(gè)空數(shù)組[]。
別的當(dāng)然還有一些內(nèi)容,譬如自定義下標(biāo),實(shí)現(xiàn)各種協(xié)議(字符串字面量協(xié)議、判等協(xié)議、比較協(xié)議、打印協(xié)議等等),代碼很優(yōu)雅,但似乎沒有我想要的東西。
最終我覺得,用 Swfit 的原生語法應(yīng)該是辦不到了,只能借助于 OC 的 runtime。主要是要用到 KVC,這樣一來所有的 Model 都得繼承自 NSObject。我寫了個(gè) Demo,從聯(lián)網(wǎng)獲取數(shù)據(jù)到顯示數(shù)據(jù)的整個(gè)流程如下:
- 先看看JSON數(shù)據(jù)的結(jié)構(gòu):

- 定義兩個(gè)Model:
class JSONModel: NSObject {
var error = ""
var count = 0
var posts = []
}
class PostModel: NSObject {
var id = 0
var date = NSDate()
var name = ""
var pic = ""
var publishtime = ""
var count = 0
var excerpt = ""
}
- 發(fā)送網(wǎng)絡(luò)請(qǐng)求(你可以使用 Alamore 或別的什么庫,我這邊是自己簡(jiǎn)單封裝了一下 NSURLSession 直接用了),然后將取得的數(shù)據(jù)先轉(zhuǎn)化成 JSONModel(直接使用
=>符號(hào)),保存到實(shí)例屬性jsonModel中:
getDataFromUrl(Constant.DemoAPI, method: .GET, parameter: nil) { data, error in
if let jsonData = data {
self.jsonModel = jsonData => JSONModel.self
}
if let httpError = error {
print(httpError)
}
}
-
jsonModel一旦被賦值就會(huì)刷新tableView,看一下配置cell的方法:
func configCell(cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {
if let model = jsonModel, post = model.posts[indexPath.section] => PostModel.self {
cell.textLabel?.text = post.excerpt
}
return cell
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Constant.ReuseIdentifier, forIndexPath: indexPath)
// Configure the cell...
return configCell(cell, indexPath: indexPath)
}
也是用=>直接把posts數(shù)組中的元素都轉(zhuǎn)化為PostModel類型的實(shí)例了,然后直接cell.textLabel?.text = post.excerpt,就把我們想顯示的內(nèi)容放到cell里了。

使用就是這么簡(jiǎn)單,只要新建一個(gè)NSObject的子類,屬性名保證跟 JSON 中的一致,并給各個(gè)屬性一個(gè)初始值。如果想另取屬性名也是可以的,用計(jì)算屬性就好了,譬如 Demo 中 publishtime 是不符合 Swift 屬性命名規(guī)范的,我們不去改原 Model,而是用一個(gè)擴(kuò)展:
extension PostModel {
var publishTime: String {
return publishtime
}
}
這雖然不是很完美(因?yàn)?code>publishtime還在),但也湊合能用了。
轉(zhuǎn)換器主要是用到了反射( Mirror 實(shí)現(xiàn))和 KVC ,代碼就不貼了,大家可以去 Github直接看源碼,clone 下來跑一下 Demo 看看。要用到自己的項(xiàng)目中的話直接把 JSONModelMapper.swift文件或者連同HTTPManager.swift一起拖到項(xiàng)目中好了,因?yàn)閷?shí)在“超輕量級(jí)”(簡(jiǎn)陋……),我覺得這樣最方便了。
覺得還算有用的話隨手點(diǎn)個(gè) Star 可好……當(dāng)然有什么意見或建議的話歡迎指教。源碼在這里。