Zhihao's Studio.

Match for iPhone开发笔记 贰 网络请求和数据流篇

Word count: 1,812 / Reading time: 7 min
2018/07/23 Share

前言

书接上文,作为一个数据驱动(Data driven)类的APP,如何从服务器端获取到数据并发送尽量少的请求次数是一个非常重要的点,甚至被定为最重要的点也不为过。这篇博客将介绍我在做Match这个应用过程中使用到的技术以及优化的方法。

网络请求库

Alamofire、Moya和Just

现有的网络请求库非常多,从OC时代大名鼎鼎AFNetworking到Swift时代的Alamofire及它的进一步封装Moya,都是对苹果网络层URLSession的封装,且有着相似的目的:将网络请求从ViewController中解耦出去。ViewController中如果混杂了太多的网络请求的构建发送响应接收响应请求的处理,会使得ViewController非常臃肿,不利于代码维护。

Moya在Alamofire的基础上进一步对网络请求进行了封装,使网络请求更容易

考虑到Alamofire和Moya引入的成本较高,我发现了另一个更容易上手的库,名字也很酷,叫Just,名字就感觉屌屌的,用起来也是出奇的好用,调用GET/POST请求Just需要一行代码,

1
2
3
4
5
6
7
8
9
10
11
// get request
Just.get("http://httpbin.org/get")
// post request
Just.post(
"http://justiceleauge.org/member/register",
data: ["username": "barryallen", "password":"ReverseF1ashSucks"],
files: ["profile_photo": .URL(fileURLWithPath:"flash.jpeg", nil)]
) { r in
if r.ok { /* success! */ }
}

但由于这个项目已经1年多没人维护了,只支持到了Swift 3,在github上发现一堆Swift 4的issue list,因此不得不再寻找其他网络请求库。这时候,SwiftHTTP出现在我视野中,并成了我最终的选择。

SwiftHTTP成了最终的选择

SiwftHTTP有着和Just类似的简洁语法,能做到一行代码解决HTTP请求。对需要快速完成整个APP开发的我来说,它能够构建请求并发送接收响应并处理,加上它的体积小、用法简单等优点,简直就是简单、可依赖最好的诠释。当然,Alamofire和Moya存在的价值是不可否定的。

网络请求库的简单流程

SwiftHTTP响应的处理

网络请求的响应都是二进制数据,具体到iOS,是NSData类型的数据,因此需要将响应结果进行编码。比如拿到的结果是一张图片,在你用UIImage对其编码(decode)之前,在计算机看来,它跟其他音频、视频甚至字符串没有什么俩样,都是一串二进制码

有过网络方面开发经验的朋友都知道,无论是响应还是请求,除了我们最想要数据部分(请求体),还有额外的开销(头部),他们告诉你这段请求成功了没有、什么原因没有成功、请求从哪里来、到哪里去等信息,真正有用的部分其实只占了一部分。这也是为什么需要尽量减少网络请求次数的原因,因为这些额外的部分,也是会带来开销的。网络请求次数太多难免造成应用卡顿,使得用户体验较差。

具体到SwiftHTTP,我获取到的响应体可能是一个String、一个Int值、一个Double值,更多的时候,是一个对象(Object),我们需要根据和后端开发者的约定,将其进行正确编码。对Object的编码是较为复杂的,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HTTP.POST(servlet_path!){ response in
if let error = response.error{
print(error)
return
}
do {
let decoder = JSONDecoder()
self.user_history = try decoder.decode(history.self, from: r.data)
}catch{
return
}
}
// history 遵从**Codable**
struct history : **Codable**{
var asked : [Int]!
var campaigning : [Int]!
var favourite : [Int]!
var phoneNumber : Int!
var replied : [Int]!
var tableIndex : Int!
var voting : [Int]!
var won : [Int]!
}

相应的处理流程是先判断有没有错误,如果有错,直接返回;没有错误的前提下,使用SwiftyJSON中的JSONDecoder对Object进行编码,需要用do和catch包围,因为这一过程不保证一定能成功。

如果结果是数组,将history.self改为[history].self即可。

需要提前申明Object的类型,且服从Codable协议。这里我选用了Strcut而不是Class,两者的具体区别感兴趣的可以自行去搜索,主要区别是Struct是值类型,Class是引用类型的。

当然,String和Int等其他基本类型也有自己的编码方法。比如String的编码方法为:String(data: r.data, encoding: String.Encoding.utf8)。

Object Model文件的生成和注意事项

Struct和Class的生成可以借助第三方工具JSONExport完成,国内有人将其进行了汉化,可以很方便的导出我们所需要的Swift model文件。

JSONExport汉化版界面

在使用JSONExport之前,网络请求的结果一般是通过PostMan获取的,有些字段的结果可能是nil,直接复制进去JSONExport就会将该字段申明为Any,但是这就让编译器为难了,因为他并不知道需要为这个字段开多大的空间。因此当你尝试让该Struct服从Codable协议时,会报错,这时就需要与后端开发人员沟通,将Any改为正确的特定类型。

从数据流角度减少网络请求数

上文已经提到了,网络请求会给系统带来很大的一笔开销,减少网络请求数是开发者在开发应用过程中优化应用的一个重要方面。这里,我给出在做Match过程中减少网络请求数比较Hack的一个小技巧,也不知道对不对,总之是有效的减少了网络请求数。

数据流(data-flow)角度看应用

鉴于在应用开发过程中反复了这种模式,这里只需举一个业务场景来说明即可。上图中涉及了三个场景,第一个场景是问题列表页,是一个TableView,数据是从服务器端获取的(默认20个)。当用户点击TableView的某个Cell,会跳转到问题详情页,包含了该问题的题干和回复部分,这部分数据是由第一个场景传过来的,至此没有再次发送获取信息的网络请求。

第三个场景是用户尝试回答这道题目,点击发送之后,回到第二个场景,这时候如果不做任何动作,用户是看不到自己刚刚的回复内容的,这很容易让用户造成困惑。但是我又不想再次发送网络请求来更新数据,于是我借助了App Delegate这个中转站,将刚刚用户回复的相关信息进行了保存,并人为的添加到了TableView的数据中。这样,在没有发送任何数据请求的前提下,做到了视图已经更新的假象

写在最后

早在写上一篇开发笔记的时候,就有读者问能否像之前的应用一样将Match开源。诚然,作为一个完整的应用,有着较大的借鉴意义,但是考虑到朋友为这个应用的开发向我支付了不菲的费用,这个应用最终版权应属于他,所以考虑再三,决定不开源,还望大家理解。

CATALOG
  1. 1. 前言
  2. 2. 网络请求库
    1. 2.1. Alamofire、Moya和Just
    2. 2.2. SwiftHTTP成了最终的选择
    3. 2.3. SwiftHTTP响应的处理
    4. 2.4. Object Model文件的生成和注意事项
  3. 3. 从数据流角度减少网络请求数
  4. 4. 写在最后