我的Swift網(wǎng)絡(luò)數(shù)據(jù)處理流

這是一篇跳票了一萬年的博客括蝠。。
網(wǎng)絡(luò)請求是iOS端非常重要的一環(huán)拨黔,雖然有著諸如Alamofire等框架的加持好乐,統(tǒng)一請求流程匾竿、簡化請求代碼依舊是需要仔細琢磨的事情

參考資料
如何處理 Swift 中的異步錯誤
陳乘方 一個 Swift 項目網(wǎng)絡(luò)層的變遷.pdf
TTReflect:json/data 轉(zhuǎn)模型

最重要的: 項目源碼

框架

Alt text
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時代卖鲤,JsonModelMJExtension畴嘶、Mantle都是人氣很高的json/model轉(zhuǎn)換框架蛋逾。年初開始探索Swift的時候,第一件事情就是尋找符合下列條件的Swift版json轉(zhuǎn)model框架

  • 模型不需要繼承其他的第三方類型
  • 模型不需要手寫映射關(guān)系
  • 支持嵌套映射

找了一圈發(fā)現(xiàn)并沒有符合要求的框架窗悯,于是自己摸索一番区匣,便有了現(xiàn)在的 TTReflect(目前已更新到2.0版本),具體用法參見Github蒋院,大概就是這樣 ↓

Alt text

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)模型

在請求方法requestsuccess回調(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>

  • 序言:七十年代末错忱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子挂据,更是在濱河造成了極大的恐慌以清,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崎逃,死亡現(xiàn)場離奇詭異掷倔,居然都是意外死亡,警方通過查閱死者的電腦和手機个绍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門今魔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勺像,“玉大人障贸,你說我怎么就攤上這事错森。” “怎么了篮洁?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我摊阀,道長披泪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任篷牌,我火速辦了婚禮睡蟋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枷颊。我一直安慰自己戳杀,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布夭苗。 她就那樣靜靜地躺著信卡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪题造。 梳的紋絲不亂的頭發(fā)上傍菇,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音界赔,去河邊找鬼丢习。 笑死,一個胖子當(dāng)著我的面吹牛淮悼,可吹牛的內(nèi)容都是我干的咐低。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼敛惊,長吁一口氣:“原來是場噩夢啊……” “哼渊鞋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞧挤,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤锡宋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后特恬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體执俩,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年癌刽,在試婚紗的時候發(fā)現(xiàn)自己被綠了役首。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尝丐。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衡奥,靈堂內(nèi)的尸體忽然破棺而出爹袁,到底是詐尸還是另有隱情,我是刑警寧澤矮固,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布失息,位于F島的核電站,受9級特大地震影響档址,放射性物質(zhì)發(fā)生泄漏盹兢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一守伸、第九天 我趴在偏房一處隱蔽的房頂上張望绎秒。 院中可真熱鬧,春花似錦尼摹、人聲如沸见芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辆童。三九已至,卻和暖如春惠赫,著一層夾襖步出監(jiān)牢的瞬間把鉴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工儿咱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庭砍,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓混埠,卻偏偏與公主長得像怠缸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钳宪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容