這是一篇跳票了一萬年的博客括蝠。。
網(wǎng)絡(luò)請求是iOS端非常重要的一環(huán)拨黔,雖然有著諸如Alamofire等框架的加持好乐,統(tǒng)一請求流程匾竿、簡化請求代碼依舊是需要仔細琢磨的事情
參考資料
如何處理 Swift 中的異步錯誤
陳乘方 一個 Swift 項目網(wǎng)絡(luò)層的變遷.pdf
TTReflect:json/data 轉(zhuǎn)模型
最重要的: 項目源碼
框架
1.網(wǎng)絡(luò)請求層
網(wǎng)絡(luò)請求層NetworkKit
主要用來封裝三方網(wǎng)絡(luò)請求庫,統(tǒng)一項目的網(wǎng)絡(luò)請求方式蔚万,降低對于三方庫的依賴性岭妖,并且負責(zé)將請求得到的data轉(zhuǎn)為json數(shù)據(jù)
2.數(shù)據(jù)處理層
數(shù)據(jù)處理層Loader
負責(zé)管理請求的鏈接、參數(shù)等笛坦,同時將json轉(zhuǎn)化為能夠直接調(diào)用的模型數(shù)組区转,盡可能為控制器減負
請求層
首先看一下重構(gòu)之前的網(wǎng)絡(luò)請求層
static func fetch(type: HttpRequestType, URLString url: String, parameters para: [String: AnyObject]? = nil, success: ((json: AnyObject) -> Void)?, error: ((statusCode: Int, json: AnyObject) -> Void)?, failure: ((error: ErrorType?) -> Void)? )
OC時代延續(xù)下來的代碼,直接傳入所有的請求參數(shù)和回調(diào)函數(shù)版扩,調(diào)用起來極不靈活
1.Fluent Interface
新的網(wǎng)絡(luò)類改用了實例方法來調(diào)用請求,并使用流式接口設(shè)計保證調(diào)用的靈活性
// 以 url侄泽、params為例
class NetworkKit {
var url: String?
var params: [String: AnyObject]?
func fetch(url: String) -> Self {
self.url = url
return self
}
func params(params: [String: AnyObject]) -> Self {
self.params = params
return self
}
}
很容易理解的代碼礁芦,每個函數(shù)保存相應(yīng)的屬性并返回自身實例,保證了鏈?zhǔn)秸{(diào)用
NetworkKit().fetch("https://tsusolo.com").params(["foo": "11"])
2.請求結(jié)果分類
與以往 成功/失敗 兩類請求結(jié)果不同的是,這里將請求結(jié)果分為三類:
- success:請求成功且返回了正確的結(jié)果柿扣,通常HTTP狀態(tài)碼為200
- error: 請求成功但返回了失敗的信息肖方,比如未找到資源、權(quán)限錯誤等
- failure: 請求失敗未状,例如網(wǎng)絡(luò)錯誤
在NetworkKit中聲明一下三種請求回調(diào)的類型俯画,補充相應(yīng)的流式接口
class NetworkKit {
typealias SuccessHandlerType = (AnyObject? -> Void)
typealias ErrorHandlerType = ((Int, AnyObject?) -> Void)
typealias FailureHandlerType = (NSError? -> Void)
var successHandler: SuccessType?
var errorHandler: ErrorType?
var failureHandler: FailureType?
// 以success為例,error司草、failure類似艰垂,詳見源碼
func success(handler: SuccessHandlerType) -> Self {
self.successHandler = handler
return self
}
}
現(xiàn)在的調(diào)用應(yīng)該是這樣子
NetworkKit().fetch("https://tsusolo.com")
.success { (json) in
print(json)
}.error { (code, errorJson) in
print(errorJson)
}.failure { (error) in
print(error)
}
請求時需要設(shè)置url、參數(shù)埋虹、header以及各類回調(diào)函數(shù)猜憎,F(xiàn)luent Interface讓NetworkKit在調(diào)用時自由增減參數(shù),保證簡潔性
3.請求和狀況處理
網(wǎng)絡(luò)請求上搔课,這里使用了Alamofire
并為此為例
var httpRequest: Request?
func request() -> Self {
if let url = url {
httpRequest = Alamofire.request(.GET, url, parameters: params, encoding: .URL, headers: headers)
.response { request, response, data, error in
let statusCode = response?.statusCode
if let statusCode = statusCode { // request success
let json: AnyObject? = data.flatMap {
return try? NSJSONSerialization.JSONObjectWithData($0, options: .MutableContainers)
}
if statusCode == 200 {
self.successHandler?(json)
} else {
self.errorHandler?(statusCode, json)
}
} else { // request failure
self.failureHandler?(error)
}
}
}
return self
}
在request
方法中胰柑,將鏈?zhǔn)秸{(diào)用保存下來的參數(shù),置入Alamofire
并發(fā)起請求爬泥,在Alamofire
的回調(diào)函數(shù)中柬讨,根據(jù)響應(yīng)結(jié)果進行分類,分別調(diào)用相應(yīng)狀況的回調(diào)函數(shù)袍啡。
1.這里使用了一個
httpRequest
保存了Alamofire的請求踩官,可用于取消請求等操作
2.結(jié)果分類需要依據(jù)具體情況,示例中根據(jù)HTTP 200狀態(tài)碼來判斷僅作為參考方式
4.示例
使用豆瓣的電影API作為示例
// 省略了error和failure的錯誤處理葬馋,看起來格外簡潔 ^^
NetworkKit().fetch("https://api.douban.com/v2/movie/subject/1764796")
.success { (json) in
print(json)
}.request()
模型映射
1.TTReflect
在Objective-C時代卖鲤,JsonModel
、MJExtension
畴嘶、Mantle
都是人氣很高的json/model轉(zhuǎn)換框架蛋逾。年初開始探索Swift的時候,第一件事情就是尋找符合下列條件的Swift版json轉(zhuǎn)model框架
- 模型不需要繼承其他的第三方類型
- 模型不需要手寫映射關(guān)系
- 支持嵌套映射
找了一圈發(fā)現(xiàn)并沒有符合要求的框架窗悯,于是自己摸索一番区匣,便有了現(xiàn)在的 TTReflect(目前已更新到2.0版本),具體用法參見Github蒋院,大概就是這樣 ↓
2.映射函數(shù)&模型的回調(diào)函數(shù)
不同的請求會返回不同的模型類型亏钩,需要為NetworkKit
聲明一個泛型,來標(biāo)明模型類型并與result
回調(diào)函數(shù)對應(yīng)欺旧。同樣使用Fluent Interface將json轉(zhuǎn)換model的映射函數(shù)和result
回調(diào)注入
class NetworkKit<Model> {
typealias ResultHandlerType = (Model -> Void)
typealias ReflectHandlerType = (AnyObject? -> Model)
var resultHanlder: ResultHandlerType?
var reflectHandler: ReflectHandlerType?
func result(handler: ResultHandlerType) -> Self {
self.resultHanlder = handler
return self
}
func reflect(f: ReflectHandlerType) -> Self {
reflectHandler = f
return self
}
}
3.回調(diào)模型
在請求方法request
的success
回調(diào)下姑丑,通過使用保存下來的映射函數(shù),將json轉(zhuǎn)換為相應(yīng)的模型辞友,并放入模型的回調(diào)中
if statusCode == 200 { // request success & response right
self.successHandler?(json)
if let reflectHandler = self.reflectHandler {
self.resultHandler?(reflectHandler(json))
}
}
e.g.g
調(diào)用時可以同時獲得json和模型(以Movie模型為例)
NetworkKit<Movie>().fetch("https://api.douban.com/v2/movie/subject/1764796")
.reflect { (json) -> Movie in
Reflect<Movie>.mapObject(json: json)
}.success({ (json) in
print(json)
}).result { (movie) in
print(movie)
}.request()
在初始化Network時聲明模型的類型栅哀,result回調(diào)函數(shù)就會返回指定類型的模型結(jié)果
Loader
當(dāng)控制器做網(wǎng)絡(luò)請求的時候震肮,是不需要知道請求細節(jié)的,只需要發(fā)出請求留拾、拿到結(jié)果就可以了戳晌。將模型聲明,請求參數(shù)和轉(zhuǎn)換方式封裝起來在loader中痴柔,減輕控制器的負擔(dān)
class MovieLoader: NetworkKit<Movie> {
func load() -> Self {
return self.fetch("https://api.douban.com/v2/movie/subject/1764796")
.reflect { (json) -> Movie in
Reflect<Movie>.mapObject(json: json)
}.request()
}
}
最后的調(diào)用就非常簡單了
MovieLoader().result { (movie) in
self.titleLabel.text = movie.title
}.error({ (code, json) in
print(code, json)
}).failure({ (error) in
print("error: ", error)
}).load()
進階
1.方法拓展
假設(shè)你的請求是下拉刷新的時候發(fā)出的沦偎,你的請求代碼可能就變得慘不忍睹
MovieLoader().result { (movie) in
tableView.endRefresh() // 停止刷新
print(movie)
}.error({ (code, json) in
tableView.endRefresh()
print(code, json)
}).failure({ (error) in
tableView.endRefresh()
print("error: ", error)
}).load()
以類似success
回調(diào)的方式,為Network
添加一個finish
回調(diào)咳蔚,在請求結(jié)束時直接調(diào)用(詳見源碼)豪嚎,將相應(yīng)操作統(tǒng)一處理
MovieLoader().finish({
tableView.endRefresh() // 停止刷新
}).result { (movie) in
print(movie)
}.error({ (code, json) in
print(code, json)
}).failure({ (error) in
print("error: ", error)
}).load()
2.復(fù)雜模型
再假設(shè)請求結(jié)果的json比較復(fù)雜,需要多個模型來承載屹篓,類似這樣 ↓
{
"aa": {
...
},
"bb": {
...
}
}
如果這里需要AA和BB兩個模型來映射到相應(yīng)的json值上疙渣,可以利用Swift元組來實現(xiàn)
class Loader: NetworkKit<(AA, BB)> {
func load() -> Self {
let f: (AnyObject? -> (AA, BB)) = { j in
let aModel = Reflect<AA>.mapObject(json: j?["aa"])
let bModel = Reflect<BB>.mapObject(json: j?["bb"])
return (aModel, bModel)
}
return self.fetch(someUrl).reflect(f).request()
}
}
這里也可以使用更上層的模型同時包裝AB類型,利用TTReflect的嵌套轉(zhuǎn)換一次性搞定堆巧,根據(jù)實際需求使用
總結(jié)
從4月swiftcon后開始構(gòu)思妄荔,歷經(jīng)多次重構(gòu),參考了各類資料谍肤,在2.0版上動用各類特性后(可見源碼 branch 2.0)啦租,最終在定稿的時候回歸樸質(zhì),用最簡單的方式來處理荒揣。
網(wǎng)絡(luò)請求是個挺復(fù)雜的問題篷角,很多時候更依賴于實際情況,就比如你遇到了一個凡事都扔給你httpStatusCode 200的奇葩后端系任,你的請求狀況分類就需要特別對待恳蹲,再比如我的項目中的error
回調(diào),返回的是錯誤json中的message字段俩滥。也因此沒有將文章的內(nèi)容變成通用的lib嘉蕾,只是提供一份思路,旨在提供更優(yōu)雅的請求方式
如果你也喜愛游戲霜旧,歡迎支持我的APP Up 游戲?qū)]?/a>