我的Swift网络数据处理流

这是一篇跳票了一万年的博客。。
网络请求是iOS端非常重要的一环,虽然有着诸如Alamofire等框架的加持,统一请求流程、简化请求代码依旧是需要仔细琢磨的事情

参考资料
如何处理 Swift 中的异步错误
陈乘方 一个 Swift 项目网络层的变迁.pdf
TTReflect:json/data 转模型

最重要的: 项目源码

框架

我的Swift网络数据处理流_第1张图片
Alt text
1.网络请求层

网络请求层NetworkKit主要用来封装三方网络请求库,统一项目的网络请求方式,降低对于三方库的依赖性,并且负责将请求得到的data转为json数据

2.数据处理层

数据处理层Loader负责管理请求的链接、参数等,同时将json转化为能够直接调用的模型数组,尽可能为控制器减负

请求层

首先看一下重构之前的网络请求层

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时代延续下来的代码,直接传入所有的请求参数和回调函数,调用起来极不灵活

1.Fluent Interface

新的网络类改用了实例方法来调用请求,并使用流式接口设计保证调用的灵活性

// 以 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
  }
}

很容易理解的代码,每个函数保存相应的属性并返回自身实例,保证了链式调用

NetworkKit().fetch("https://tsusolo.com").params(["foo": "11"])
2.请求结果分类

与以往 成功/失败 两类请求结果不同的是,这里将请求结果分为三类:

  • success:请求成功且返回了正确的结果,通常HTTP状态码为200
  • error: 请求成功但返回了失败的信息,比如未找到资源、权限错误等
  • failure: 请求失败,例如网络错误

在NetworkKit中声明一下三种请求回调的类型,补充相应的流式接口

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
  }
}

现在的调用应该是这样子

NetworkKit().fetch("https://tsusolo.com")
.success { (json) in
  print(json)
}.error { (code, errorJson) in
  print(errorJson)
}.failure { (error) in
  print(error)
}

请求时需要设置url、参数、header以及各类回调函数,Fluent Interface让NetworkKit在调用时自由增减参数,保证简洁性

3.请求和状况处理

网络请求上,这里使用了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方法中,将链式调用保存下来的参数,置入Alamofire并发起请求,在Alamofire的回调函数中,根据响应结果进行分类,分别调用相应状况的回调函数。

1.这里使用了一个httpRequest保存了Alamofire的请求,可用于取消请求等操作
2.结果分类需要依据具体情况,示例中根据HTTP 200状态码来判断仅作为参考方式

4.示例

使用豆瓣的电影API作为示例

// 省略了error和failure的错误处理,看起来格外简洁 ^^
NetworkKit().fetch("https://api.douban.com/v2/movie/subject/1764796")
.success { (json) in
  print(json)
}.request()

模型映射

1.TTReflect

在Objective-C时代,JsonModelMJExtensionMantle都是人气很高的json/model转换框架。年初开始探索Swift的时候,第一件事情就是寻找符合下列条件的Swift版json转model框架

  • 模型不需要继承其他的第三方类型
  • 模型不需要手写映射关系
  • 支持嵌套映射

找了一圈发现并没有符合要求的框架,于是自己摸索一番,便有了现在的 TTReflect(目前已更新到2.0版本),具体用法参见Github,大概就是这样 ↓

我的Swift网络数据处理流_第2张图片
Alt text

2.映射函数&模型的回调函数

不同的请求会返回不同的模型类型,需要为NetworkKit声明一个泛型,来标明模型类型并与result回调函数对应。同样使用Fluent Interface将json转换model的映射函数和result回调注入

class NetworkKit {
  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.回调模型

在请求方法requestsuccess回调下,通过使用保存下来的映射函数,将json转换为相应的模型,并放入模型的回调中

if statusCode == 200 {          // request success & response right
  self.successHandler?(json)
  if let reflectHandler = self.reflectHandler {
    self.resultHandler?(reflectHandler(json))
  }
}

e.g.g
调用时可以同时获得json和模型(以Movie模型为例)

NetworkKit().fetch("https://api.douban.com/v2/movie/subject/1764796")
.reflect { (json) -> Movie in
  Reflect.mapObject(json: json)
}.success({ (json) in
  print(json)
}).result { (movie) in
  print(movie)
}.request()

在初始化Network时声明模型的类型,result回调函数就会返回指定类型的模型结果

Loader

当控制器做网络请求的时候,是不需要知道请求细节的,只需要发出请求、拿到结果就可以了。将模型声明,请求参数和转换方式封装起来在loader中,减轻控制器的负担

class MovieLoader: NetworkKit {
  func load() -> Self {
    return self.fetch("https://api.douban.com/v2/movie/subject/1764796")
    .reflect { (json) -> Movie in
      Reflect.mapObject(json: json)
    }.request()
  }
}

最后的调用就非常简单了

MovieLoader().result { (movie) in
  self.titleLabel.text = movie.title
}.error({ (code, json) in
  print(code, json)
}).failure({ (error) in
  print("error: ", error)
}).load()

进阶

1.方法拓展

假设你的请求是下拉刷新的时候发出的,你的请求代码可能就变得惨不忍睹

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回调的方式,为Network添加一个finish回调,在请求结束时直接调用(详见源码),将相应操作统一处理

MovieLoader().finish({
  tableView.endRefresh() // 停止刷新
}).result { (movie) in
  print(movie)
}.error({ (code, json) in
  print(code, json)
}).failure({ (error) in
  print("error: ", error)
}).load()
2.复杂模型

再假设请求结果的json比较复杂,需要多个模型来承载,类似这样 ↓

{
    "aa": {
        ...
    },
    "bb": {
        ...
    }
}

如果这里需要AA和BB两个模型来映射到相应的json值上,可以利用Swift元组来实现

class Loader: NetworkKit<(AA, BB)> {
  func load() -> Self {
    let f: (AnyObject? -> (AA, BB)) = { j in
      let aModel = Reflect.mapObject(json: j?["aa"])
      let bModel = Reflect.mapObject(json: j?["bb"])
      return (aModel, bModel)
    }
    return self.fetch(someUrl).reflect(f).request()
  }
}

这里也可以使用更上层的模型同时包装AB类型,利用TTReflect的嵌套转换一次性搞定,根据实际需求使用

总结

从4月swiftcon后开始构思,历经多次重构,参考了各类资料,在2.0版上动用各类特性后(可见源码 branch 2.0),最终在定稿的时候回归朴质,用最简单的方式来处理。

网络请求是个挺复杂的问题,很多时候更依赖于实际情况,就比如你遇到了一个凡事都扔给你httpStatusCode 200的奇葩后端,你的请求状况分类就需要特别对待,再比如我的项目中的error回调,返回的是错误json中的message字段。也因此没有将文章的内容变成通用的lib,只是提供一份思路,旨在提供更优雅的请求方式


如果你也喜爱游戏,欢迎支持我的APP Up 游戏专辑

我的Swift网络数据处理流_第3张图片

你可能感兴趣的:(我的Swift网络数据处理流)