2017.07.05更新:
- 移除
Alamofire
不支持iOS8解決方案轨帜,Alamofire
在我當(dāng)時項目立項的時候是不支持iOS8的哎迄,所以引入了不支持iOS8解決方案,但是之后不久就不固執(zhí)的要求 iOS9+ 了唱矛,所以這篇博客里的解決方案部分就沒有什么意義了缺前,徒增篇幅,也就移除了监氢。 - 增加了Demo 布蔗,點擊傳送至Demo
- 增加了HanyJSON
Alamofire
在Swift中我們發(fā)送網(wǎng)絡(luò)請求一般都是使用一個第三方庫 Alamofire ,設(shè)置好URL和parameter然后發(fā)送網(wǎng)絡(luò)請求浪腐,就像下面這樣:
// 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)
}
當(dāng)然這是Alamofire
官方給出的最簡單使用例子纵揍,你要真的在項目里每個網(wǎng)絡(luò)請求都這么寫。议街。那真的是太耿直了泽谨,來一次網(wǎng)絡(luò)庫版本更新,你就知道什么是痛苦了特漩,項目里遍地都是要改的代碼吧雹。
給Alamofire加個Router
我們在項目開發(fā)中,很少像上邊那樣直接拿來用涂身,這篇不是Alamofire
的教程雄卷,感興趣的可以到 這里 (這里是國人翻譯版)看看是如何從零開始一步步把Alamofire
封裝成一個更加好用的框架的。
如果只想用 Alamofire
不想引入 Moya
在我的demo里的Router.swift
文件 也提供好了類似的封裝蛤售,可以直接復(fù)制丁鹉,稍微修改一下就可以拿來用。點擊跳轉(zhuǎn)
使用封裝好的Alamofire
只需要這樣:
// get請求 根據(jù)不同需求悍抑,可以在router里 自定義網(wǎng)絡(luò)請求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info鳄炉,然后處理請求到的response
Alamofire.request(Router.getInfo(uid: "1", token: "abc")).responseJSON { response in
// 在這里處理 請求到的數(shù)據(jù)
debugPrint(response)
}
// post請求 根據(jù)不同需求,可以在router里 自定義網(wǎng)絡(luò)請求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info搜骡,然后處理請求到的response
Alamofire.request(Router.postInfo(uid: "1", info: "110")).responseJSON { response in
// 在這里處理 請求到的數(shù)據(jù)
debugPrint(response)
}
比上邊的直接用看起來簡潔多了拂盯。
Moya
其實Moya
已經(jīng)把我們上邊做的都寫好了,使用moya
進行網(wǎng)絡(luò)請求是這樣的:
provider.request(.zen) { result in
...
...
}
也很簡潔记靡。同樣在我的demo里的Moya.swift
文件 也提供好了Moya
在項目里的寫法谈竿,可以直接復(fù)制团驱,稍微修改一下就可以拿來用。點擊跳轉(zhuǎn)空凸,在項目里使用見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'
發(fā)起網(wǎng)絡(luò)請求使用起來和單獨使用Moya差不多:
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider(.login(username: "username", password: "password")) { result in
debugPrint(result)
}
但是紊选,配合起來Rx,Moya
提供了更多的方便用法道逗。
- 比如我們剛才寫的代碼兵罢,其實對請求到的錯誤信息都沒有處理。我們現(xiàn)在可以這樣寫:
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)
- 一句話把請求到的數(shù)據(jù)轉(zhuǎn)成JSON
rxProvider.request(.login(username: "username", password: "password"))
.mapJSON()
.subscribe(onNext: { json in
debugPrint(json) //已經(jīng)幫你轉(zhuǎn)成了 JSON卖词,在這里只需要拿到JSON進行接下來的邏輯就可以了
})
.addDisposableTo(disposeBag)
- 還可以mapString
.mapString(atKeyPath: "")
Moya + RxSwift + ObjectMapper
在上一部分,我們已經(jīng)可以將網(wǎng)絡(luò)請求回來的數(shù)據(jù)一鍵轉(zhuǎn)為JSON了吏夯,項目中肯定是經(jīng)常會遇到將JSON轉(zhuǎn)為對象的操作此蜈,ObjectMapper 就是Swift中常用的JSON轉(zhuǎn)對象框架。
將JSON轉(zhuǎn)為對象:
let object = Mapper<T>().map(JSON: data)!
具體詳細用法可以看看項目說明噪生,這里默認已經(jīng)是有過Swift項目經(jīng)驗裆赵,使用過ObjectMapper,就不講解ObjectMapper的用法了杠园,因為如果寫出來顾瞪,這篇文章就太長了。如果你用的是HandyJson抛蚁,請看下一節(jié)的介紹陈醒。
接下來,我們把json解析為對象的過程也簡化一下瞧甩。
我們請求回來的數(shù)據(jù)可能是這樣的:
{
"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
對象數(shù)組
ObjectMapper + RxSwift
在Podfile
中添加:
pod 'ObjectMapper', '~> 2.2'
然后記得pod install
。
接下來肚逸,創(chuàng)建RxMoyaMapper.swift
文件
因為網(wǎng)絡(luò)請求和解析的過程中爷辙,會有各種錯誤出現(xiàn),首先要在文件中定義錯誤類型
enum DCUError : Swift.Error {
// 解析失敗
case ParseJSONError
// 網(wǎng)絡(luò)請求發(fā)生錯誤
case RequestFailed
// 接收到的返回沒有data
case NoResponse
//服務(wù)器返回了一個錯誤代碼
case UnexpectedResult(resultCode: String?, resultMsg: String?)
}
然后定義一個RequestStatus
的枚舉朦促,用來表示服務(wù)器返回給我們的code是不是約定的成功code膝晾,比方約定返回200
為認證成功,401
表示用戶登錄信息失效务冕,等等血当,那么我們是需要在返回認證不成功code時,進行一些邏輯處理的(比方彈出登錄框,讓用戶重新登錄)臊旭。
enum RequestStatus: Int {
case requestSuccess = 200
case requestError
}
定義 code ,message ,data字段落恼,方便后邊解析時候使用(請根據(jù)后臺返回內(nèi)容不同做相應(yīng)修改,這里是拿我們后臺的返回規(guī)則舉例):
let RESULT_CODE = "code"
let RESULT_MSG = "message"
let RESULT_DATA = "data"
然后我們給RxSwift 的Observable
添加extension离熏,兩個方法分別對應(yīng)解析為一個單獨的User
對象佳谦,和解析成一個User
對象數(shù)組:
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
文件, 也提供好了完整的實現(xiàn)滋戳,可以現(xiàn)在項目看一下钻蔑,稍微修改一下就可以拿來在項目里使用。點擊跳轉(zhuǎn)
在項目里寫起來是這樣的:
//解析成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對象數(shù)組
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
來用胧瓜,其他的實現(xiàn)也都很類似矢棚,只需要把解析的部分換成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對象數(shù)組
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObjectArray(type: People.self)
.subscribe(onNext: { users in
debugPrint(users)
})
.addDisposableTo(disposeBag)
}
同樣,handyJSON的代碼我也放在我的demo里的RxHandyJSON.swift
文件了府喳, 提供好了完整的實現(xiàn),可以現(xiàn)在項目看一下蘑拯,稍微修改一下就可以拿來在項目里使用钝满。點擊跳轉(zhuǎn)