2017.07.05更新:
- 移除
Alamofire不支持iOS8解決方案,Alamofire在我當(dāng)時(shí)項(xiàng)目立項(xiàng)的時(shí)候是不支持iOS8的,所以引入了不支持iOS8解決方案,但是之后不久就不固執(zhí)的要求 iOS9+ 了,所以這篇博客里的解決方案部分就沒(méi)有什么意義了,徒增篇幅,也就移除了。 - 增加了Demo ,點(diǎn)擊傳送至Demo
- 增加了HanyJSON
Alamofire
在Swift中我們發(fā)送網(wǎng)絡(luò)請(qǐng)求一般都是使用一個(gè)第三方庫(kù) Alamofire ,設(shè)置好URL和parameter然后發(fā)送網(wǎng)絡(luò)請(qǐng)求,就像下面這樣:
// 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官方給出的最簡(jiǎn)單使用例子,你要真的在項(xiàng)目里每個(gè)網(wǎng)絡(luò)請(qǐng)求都這么寫。。那真的是太耿直了,來(lái)一次網(wǎng)絡(luò)庫(kù)版本更新,你就知道什么是痛苦了,項(xiàng)目里遍地都是要改的代碼。
給Alamofire加個(gè)Router
我們?cè)陧?xiàng)目開(kāi)發(fā)中,很少像上邊那樣直接拿來(lái)用,這篇不是Alamofire的教程,感興趣的可以到 這里 (這里是國(guó)人翻譯版)看看是如何從零開(kāi)始一步步把Alamofire封裝成一個(gè)更加好用的框架的。
如果只想用 Alamofire不想引入 Moya 在我的demo里的Router.swift文件 也提供好了類似的封裝,可以直接復(fù)制,稍微修改一下就可以拿來(lái)用。點(diǎn)擊跳轉(zhuǎn)
使用封裝好的Alamofire只需要這樣:
// get請(qǐng)求 根據(jù)不同需求,可以在router里 自定義網(wǎng)絡(luò)請(qǐng)求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info,然后處理請(qǐng)求到的response
Alamofire.request(Router.getInfo(uid: "1", token: "abc")).responseJSON { response in
// 在這里處理 請(qǐng)求到的數(shù)據(jù)
debugPrint(response)
}
// post請(qǐng)求 根據(jù)不同需求,可以在router里 自定義網(wǎng)絡(luò)請(qǐng)求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info,然后處理請(qǐng)求到的response
Alamofire.request(Router.postInfo(uid: "1", info: "110")).responseJSON { response in
// 在這里處理 請(qǐng)求到的數(shù)據(jù)
debugPrint(response)
}
比上邊的直接用看起來(lái)簡(jiǎn)潔多了。
Moya
其實(shí)Moya已經(jīng)把我們上邊做的都寫好了,使用moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求是這樣的:
provider.request(.zen) { result in
...
...
}
也很簡(jiǎn)潔。同樣在我的demo里的Moya.swift文件 也提供好了Moya在項(xiàng)目里的寫法,可以直接復(fù)制,稍微修改一下就可以拿來(lái)用。點(diǎn)擊跳轉(zhuǎn),在項(xiàng)目里使用見(jiàn)ViewController.swift,是這樣的:
let provider = MoyaProvider<MyAPI>()
provider.request(.login(username: "username", password: "password")) { result in
debugPrint(result)
}
Moya + RxSwift
接下來(lái)我們看看 Moya配合上RxSwift能有什么不同的魔法。因?yàn)槲覀円玫氖?code>Moya提供的Rx擴(kuò)展,所以Podfile里要這樣寫:
pod 'Moya/RxSwift'
發(fā)起網(wǎng)絡(luò)請(qǐng)求使用起來(lái)和單獨(dú)使用Moya差不多:
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider(.login(username: "username", password: "password")) { result in
debugPrint(result)
}
但是,配合起來(lái)Rx,Moya提供了更多的方便用法。
- 比如我們剛才寫的代碼,其實(shí)對(duì)請(qǐng)求到的錯(cuò)誤信息都沒(méi)有處理。我們現(xiàn)在可以這樣寫:
rxProvider.request(.login(username: "username", password: "password"))
.filterSuccessfulStatusCodes()
.subscribe(onNext: {
debugPrint($0)
})
.addDisposableTo(disposeBag)
- 想要過(guò)濾出特定的code?比如說(shuō)在用戶密碼更改時(shí)和后臺(tái)約定的code:
rxProvider.request(.login(username: "username", password: "password"))
.filter(statusCode: 0)
.subscribe(onNext: {
debugPrint($0)
//在這里添加處理返回特定code的邏輯
})
.addDisposableTo(disposeBag)
- 還有提供過(guò)濾 一個(gè)范圍的code:
.filter(statusCodes: 200...300)
- 一句話把請(qǐng)求到的數(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進(jìn)行接下來(lái)的邏輯就可以了
})
.addDisposableTo(disposeBag)
- 還可以mapString
.mapString(atKeyPath: "")
Moya + RxSwift + ObjectMapper
在上一部分,我們已經(jīng)可以將網(wǎng)絡(luò)請(qǐng)求回來(lái)的數(shù)據(jù)一鍵轉(zhuǎn)為JSON了,項(xiàng)目中肯定是經(jīng)常會(huì)遇到將JSON轉(zhuǎn)為對(duì)象的操作,ObjectMapper 就是Swift中常用的JSON轉(zhuǎn)對(duì)象框架。
將JSON轉(zhuǎn)為對(duì)象:
let object = Mapper<T>().map(JSON: data)!
具體詳細(xì)用法可以看看項(xiàng)目說(shuō)明,這里默認(rèn)已經(jīng)是有過(guò)Swift項(xiàng)目經(jīng)驗(yàn),使用過(guò)ObjectMapper,就不講解ObjectMapper的用法了,因?yàn)槿绻麑懗鰜?lái),這篇文章就太長(zhǎng)了。如果你用的是HandyJson,請(qǐng)看下一節(jié)的介紹。
接下來(lái),我們把json解析為對(duì)象的過(guò)程也簡(jiǎn)化一下。
我們請(qǐng)求回來(lái)的數(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解析為一個(gè)單獨(dú)的User對(duì)象,也要能解析成一個(gè)User對(duì)象數(shù)組
ObjectMapper + RxSwift
在Podfile中添加:
pod 'ObjectMapper', '~> 2.2'
然后記得pod install。
接下來(lái),創(chuàng)建RxMoyaMapper.swift文件
因?yàn)榫W(wǎng)絡(luò)請(qǐng)求和解析的過(guò)程中,會(huì)有各種錯(cuò)誤出現(xiàn),首先要在文件中定義錯(cuò)誤類型
enum DCUError : Swift.Error {
// 解析失敗
case ParseJSONError
// 網(wǎng)絡(luò)請(qǐng)求發(fā)生錯(cuò)誤
case RequestFailed
// 接收到的返回沒(méi)有data
case NoResponse
//服務(wù)器返回了一個(gè)錯(cuò)誤代碼
case UnexpectedResult(resultCode: String?, resultMsg: String?)
}
然后定義一個(gè)RequestStatus的枚舉,用來(lái)表示服務(wù)器返回給我們的code是不是約定的成功code,比方約定返回200為認(rèn)證成功,401表示用戶登錄信息失效,等等,那么我們是需要在返回認(rèn)證不成功code時(shí),進(jìn)行一些邏輯處理的(比方彈出登錄框,讓用戶重新登錄)。
enum RequestStatus: Int {
case requestSuccess = 200
case requestError
}
定義 code ,message ,data字段,方便后邊解析時(shí)候使用(請(qǐng)根據(jù)后臺(tái)返回內(nèi)容不同做相應(yīng)修改,這里是拿我們后臺(tái)的返回規(guī)則舉例):
let RESULT_CODE = "code"
let RESULT_MSG = "message"
let RESULT_DATA = "data"
然后我們給RxSwift 的Observable添加extension,兩個(gè)方法分別對(duì)應(yīng)解析為一個(gè)單獨(dú)的User對(duì)象,和解析成一個(gè)User對(duì)象數(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文件, 也提供好了完整的實(shí)現(xiàn),可以現(xiàn)在項(xiàng)目看一下,稍微修改一下就可以拿來(lái)在項(xiàng)目里使用。點(diǎn)擊跳轉(zhuǎn)
在項(xiàng)目里寫起來(lái)是這樣的:
//解析成User對(duì)象
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObject(type: User.self)
.subscribe(onNext: { user in
debugPrint(user)
})
.addDisposableTo(disposeBag)
//解析成User對(duì)象數(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
我們可以直接來(lái)之前定義的DCUError來(lái)用,其他的實(shí)現(xiàn)也都很類似,只需要把解析的部分換成handyJSON就行了:
let object = JSONDeserializer<T>.deserializeFrom(json: jsonString)
let objArray = JSONDeserializer<T>.deserializeModelArrayFrom(array: objectsArrays)
使用起來(lái)也是一模一樣的:
func handyJSON() {
//解析成People對(duì)象
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObject(type: People.self)
.subscribe(onNext: { _ in
})
.addDisposableTo(disposeBag)
//解析成People對(duì)象數(shù)組
rxProvider.request(.login(username: "username", password: "password"))
.mapResponseToObjectArray(type: People.self)
.subscribe(onNext: { users in
debugPrint(users)
})
.addDisposableTo(disposeBag)
}
同樣,handyJSON的代碼我也放在我的demo里的RxHandyJSON.swift文件了, 提供好了完整的實(shí)現(xiàn),可以現(xiàn)在項(xiàng)目看一下,稍微修改一下就可以拿來(lái)在項(xiàng)目里使用。點(diǎn)擊跳轉(zhuǎn)