2017.07.05更新:
- 移除
Alamofire
不支持iOS8解决方案,Alamofire
在我当时项目立项的时候是不支持iOS8的,所以引入了不支持iOS8解决方案,但是之后不久就不固执的要求 iOS9+ 了,所以这篇博客里的解决方案部分就没有什么意义了,徒增篇幅,也就移除了。 - 增加了Demo ,点击传送至Demo
- 增加了HanyJSON
Alamofire
在Swift中我们发送网络请求一般都是使用一个第三方库 Alamofire ,设置好URL和parameter然后发送网络请求,就像下面这样:
// Alamofire 4
let parameters: Parameters = ["foo": "bar"]
Alamofire.request(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
}
当然这是Alamofire
官方给出的最简单使用例子,你要真的在项目里每个网络请求都这么写。。那真的是太耿直了,来一次网络库版本更新,你就知道什么是痛苦了,项目里遍地都是要改的代码。
给Alamofire加个Router
我们在项目开发中,很少像上边那样直接拿来用,这篇不是Alamofire
的教程,感兴趣的可以到 这里 (这里是国人翻译版)看看是如何从零开始一步步把Alamofire
封装成一个更加好用的框架的。
如果只想用 Alamofire
不想引入 Moya
在我的demo里的Router.swift
文件 也提供好了类似的封装,可以直接复制,稍微修改一下就可以拿来用。点击跳转
使用封装好的Alamofire
只需要这样:
// get请求 根据不同需求,可以在router里 自定义网络请求需要的字段, 业务代码只需要关心传入uid 和 info,然后处理请求到的response
Alamofire.request(Router.getInfo(uid: "1", token: "abc")).responseJSON { response in
// 在这里处理 请求到的数据
debugPrint(response)
}
// post请求 根据不同需求,可以在router里 自定义网络请求需要的字段, 业务代码只需要关心传入uid 和 info,然后处理请求到的response
Alamofire.request(Router.postInfo(uid: "1", info: "110")).responseJSON { response in
// 在这里处理 请求到的数据
debugPrint(response)
}
比上边的直接用看起来简洁多了。
Moya
其实Moya
已经把我们上边做的都写好了,使用moya
进行网络请求是这样的:
provider.request(.zen) { result in
...
...
}
也很简洁。同样在我的demo里的Moya.swift
文件 也提供好了Moya
在项目里的写法,可以直接复制,稍微修改一下就可以拿来用。点击跳转,在项目里使用见ViewController.swift
,是这样的:
let provider = MoyaProvider<MyAPI>()
provider.request(.login(username: "username", password: "password")) { result in
debugPrint(result)
}
Moya + RxSwift
接下来我们看看 Moya
配合上RxSwift
能有什么不同的魔法。因为我们要用的是Moya
提供的Rx扩展,所以Podfile
里要这样写:
pod 'Moya/RxSwift'
发起网络请求使用起来和单独使用Moya差不多:
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider(.login(username: "username", password: "password")) { result in
debugPrint(result)
}
但是,配合起来Rx,Moya
提供了更多的方便用法。
- 比如我们刚才写的代码,其实对请求到的错误信息都没有处理。我们现在可以这样写:
rxProvider.request(.login(username: "username", password: "password"))
.filterSuccessfulStatusCodes()
.subscribe(onNext: {
debugPrint($0)
})
.addDisposableTo(disposeBag)
- 想要过滤出特定的code?比如说在用户密码更改时和后台约定的code:
rxProvider.request(.login(username: "username", password: "password"))
.filter(statusCode: 0)
.subscribe(onNext: {
debugPrint($0)
//在这里添加处理返回特定code的逻辑
})
.addDisposableTo(disposeBag)
- 还有提供过滤 一个范围的code:
.filter(statusCodes: 200...300)
- 一句话把请求到的数据转成JSON
rxProvider.request(.login(username: "username", password: "password"))
.mapJSON()
.subscribe(onNext: { json in
debugPrint(json) //已经帮你转成了 JSON,在这里只需要拿到JSON进行接下来的逻辑就可以了
})
.addDisposableTo(disposeBag)
- 还可以mapString
.mapString(atKeyPath: "")
Moya + RxSwift + ObjectMapper
在上一部分,我们已经可以将网络请求回来的数据一键转为JSON了,项目中肯定是经常会遇到将JSON转为对象的操作,ObjectMapper 就是Swift中常用的JSON转对象框架。
将JSON转为对象:
let object = Mapper<T>().map(JSON: data)!
具体详细用法可以看看项目说明,这里默认已经是有过Swift项目经验,使用过ObjectMapper,就不讲解ObjectMapper的用法了,因为如果写出来,这篇文章就太长了。如果你用的是HandyJson,请看下一节的介绍。
接下来,我们把json解析为对象的过程也简化一下。
我们请求回来的数据可能是这样的:
{
"code" : 200
"message" : "success"
"data" : {
"user" : {
"nickname" : "name"
"age" : 18
}
}
}
也可能是这样的:
{
"code" : 200
"message" : "success"
"data" : [
"user" : {
"nickname" : "name1"
"age" : 18
},
"user" : {
"nickname" : "name2"
"age" : 19
},
"user" : {
"nickname" : "name3"
"age" : 13
}
]
}
这就要求我们能把JSON解析为一个单独的User
对象,也要能解析成一个User
对象数组
ObjectMapper + RxSwift
在Podfile
中添加:
pod 'ObjectMapper', '~> 2.2'
然后记得pod install
。
接下来,创建RxMoyaMapper.swift
文件
因为网络请求和解析的过程中,会有各种错误出现,首先要在文件中定义错误类型
enum DCUError : Swift.Error {
// 解析失败
case ParseJSONError
// 网络请求发生错误
case RequestFailed
// 接收到的返回没有data
case NoResponse
//服务器返回了一个错误代码
case UnexpectedResult(resultCode: String?, resultMsg: String?)
}
然后定义一个RequestStatus
的枚举,用来表示服务器返回给我们的code是不是约定的成功code,比方约定返回200
为认证成功,401
表示用户登录信息失效,等等,那么我们是需要在返回认证不成功code时,进行一些逻辑处理的(比方弹出登录框,让用户重新登录)。
enum RequestStatus: Int {
case requestSuccess = 200
case requestError
}
定义 code ,message ,data字段,方便后边解析时候使用(请根据后台返回内容不同做相应修改,这里是拿我们后台的返回规则举例):
let RESULT_CODE = "code"
let RESULT_MSG = "message"
let RESULT_DATA = "data"
然后我们给RxSwift 的Observable
添加extension,两个方法分别对应解析为一个单独的User
对象,和解析成一个User
对象数组:
extension Observable {
func mapResponseToObject<T: BaseMappable>(type: T.Type) -> Observable<T> {
return map { response in
// get Moya.Response
guard let response = response as? Moya.Response else {
throw DCUError.NoResponse
}
// check http status
guard ((200...209) ~= response.statusCode) else {
throw DCUError.RequestFailed
}
guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any] else {
throw DCUError.NoResponse
}
if let code = json[RESULT_CODE] as? Int {
if code == RequestStatus.requestSuccess.rawValue {
let data = json[RESULT_DATA]
if let data = data as? [String: Any] {
let object = Mapper<T>().map(JSON: data)!
return object
}else {
throw DCUError.ParseJSONError
}
} else {
throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)
}
} else {
throw DCUError.ParseJSONError
}
}
}
func mapResponseToObjectArray<T: BaseMappable>(type: T.Type) -> Observable<[T]> {
return map { response in
// get Moya.Response
guard let response = response as? Moya.Response else {
throw DCUError.NoResponse
}
// check http status
guard ((200...209) ~= response.statusCode) else {
throw DCUError.RequestFailed
}
guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any] else {
throw DCUError.NoResponse
}
if let code = json[RESULT_CODE] as? Int {
if code == RequestStatus.requestSuccess.rawValue {
var objects = [T]()
guard let objectsArrays = json[RESULT_DATA] as? [Any] else {
throw DCUError.ParseJSONError
}
for object in objectsArrays {
if let data = object as? [String: Any] {
let object = Mapper<T>().map(JSON: data)!
objects.append(object)
}
}
return objects
} else {
throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)
}
} else {
throw DCUError.ParseJSONError
}
}
}
}
同样在我的demo里的RxMoyaMapper.swift
文件, 也提供好了完整的实现,可以现在项目看一下,稍微修改一下就可以拿来在项目里使用。点击跳转
在项目里写起来是这样的:
//解析成User对象
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObject(type: User.self)
.subscribe(onNext: { user in
debugPrint(user)
})
.addDisposableTo(disposeBag)
//解析成User对象数组
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObjectArray(type: User.self)
.subscribe(onNext: { users in
debugPrint(users)
})
.addDisposableTo(disposeBag)
Moya + RxSwift + ObjectMapper
首先在Podfile
中添加:
pod 'HandyJSON', '~> 1.7.2'
然后
pod install
我们可以直接来之前定义的DCUError
来用,其他的实现也都很类似,只需要把解析的部分换成handyJSON就行了:
let object = JSONDeserializer<T>.deserializeFrom(json: jsonString)
let objArray = JSONDeserializer<T>.deserializeModelArrayFrom(array: objectsArrays)
使用起来也是一模一样的:
func handyJSON() {
//解析成People对象
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObject(type: People.self)
.subscribe(onNext: { _ in
})
.addDisposableTo(disposeBag)
//解析成People对象数组
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObjectArray(type: People.self)
.subscribe(onNext: { users in
debugPrint(users)
})
.addDisposableTo(disposeBag)
}
同样,handyJSON的代码我也放在我的demo里的RxHandyJSON.swift
文件了, 提供好了完整的实现,可以现在项目看一下,稍微修改一下就可以拿来在项目里使用。点击跳转