Swift中優雅的網路請求:Moya+RxSwift
Moya是一個對Alamofire封裝的庫,提供簡潔的介面供開發者呼叫,抽象了URL和Parameters來幫助使用者生成urlRequest,最後通過alamofire發起請求。
具體使用時在Moya和Your App之間加一層Rx,用於處理請求回來的資料
先來看看Moya的具體實現和使用方式
Moya使用的是面向協議(POP)程式設計的思想
//可以從發起網路請求看起,碰到對應的協議或類再回來看具體細節
Moya中的協議包括:
1.TargetType協議,用來生成request的必要引數
baseURL
pathmethod
sampleData:做測試模擬的資料
task:生成具體的task,可以傳入請求的引數
headers:
Moya中的類包括:
Class1 Endpoint:用來靈活配置網路請求
property list:
- url
- method
- task
- sampleData
- SampleResponseClosure
- HttpHeaderFields
和TargetType協議的元素意義對應,在框架中通過TargetType初始化Endpoint類
method list:
- methodF: urlRequest() -> URLRequest
- adding(newHTTPHeaderFields:[String:String]) -> Endpoint 新增新的Header返回新的Endpoint
- replacing(task:Task)-> Endpoint 替換task返回新的Endpoint
Class2 MoyaProvider<Target: TargetType>: MoyaProviderType
method list :
-
methodA: defaultEndpointMapping(for target:Target) -> Endpoint ,通過TargetType型別生成Endpoint
-
methodB:defaultRequestMapping(for target:Endpoint, closure: RequestResultClosure) ,呼叫 methodF 通過Endpoint 中methodF生成URLRequest,根據生成情況執行RequestResultClosure
-
methodC:performRequest(很多引數)根據endpoint中不同的task呼叫不同的發起請求的方法,其中包含methodD
-
methodD:sendRequest()傳入URLRequest,呼叫Alamofire方法生成Alamofire發起請求的DataRequest型別,傳入並呼叫methodE
-
methodE:sendAlamofireRequest() 傳入DataRequest,用Alamofire發起請求
發起網路請求的過程:
1.初始化各類
- 1.建立列舉型別,實現TargetType協議的方法。使用列舉的原因:使用列舉結合switch管理api更加方便。具體可以檢視《Swift的列舉》
- 2.用TargetType、EndpointClosure、requestClosure 初始化MoyaProvider類,初始化時可以提供endpointClosure,型別為 (Target) -> Endpoint,不傳使用defaultEndpointMapping
- 3.可以自定義requestClosure,來自定義URL生成時候的錯誤情況處理。比如引數錯誤,加密錯誤等
2.生成URLreqeust
- 1.用EndpointClosure傳入 TargetType建立Endpoint,
- 2.建立closure1:performNetworking(RequestResutlClosure型別),closure1內部執行methodC
- 3.methodB:requestClosure:傳入步驟1生成的endpoint ,步驟2生成的performNetworking,執行methodF生成URLRequest
3.發起請求
- methodB 執行clousre1
- clousre1 執行methodC
- methodD
- methodE
// methodB
public final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
do {
let urlRequest = try endpoint.urlRequest()
closure(.success(urlRequest))
} catch MoyaError.requestMapping(let url) {
closure(.failure(MoyaError.requestMapping(url)))
} catch MoyaError.parameterEncoding(let error) {
closure(.failure(MoyaError.parameterEncoding(error)))
} catch {
closure(.failure(MoyaError.underlying(error, nil)))
}
}
Moya框架中的POP:
- protocol MoyaProviderType
我們可以實現MoyaProviderType中的request方法,來自定義發起請求的方式 - protocol TargetType
定義了發起請求需要的引數
Moya和RxSwift:
通過下面的程式碼把Moya和Rx進行結合,解決callback hell的問題
- 通過相同的方法可以給類新增rx支援,具體檢視另一篇RxSwift的文章
public func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Single<Response> {
return Single.create { [weak base] single in
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
single(.success(response))
case let .failure(error):
single(.error(error))
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
}
- callback hell如下圖
Moya和資料模型:
response有解析Decodable的模型資料預設實現,可以直接使用,也可以自定義map的實現,來根據專案需求自定義解析過程(比如使用MJ),根據不同狀況丟擲error。
下面為自定義的程式碼:
extension Response {
func mapModel<T: Codable>(_ type: T.Type) throws -> T {
print(String.init(data: data, encoding: .utf8) ?? "")
guard let json = try mapJSON() as? [String: Any] else {
throw MoyaError.jsonMapping(self)
}
//這裡可以新增不同情況的報錯
// if(error) {
// throw MoyaError.jsonMapping(self)
// }
return (type as! NSObject.Type).mj_object(withKeyValues: json["data"]) as! T
}
}
public extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
func MJObjectMap<T>(_ type: T.Type,_ handleErr:Bool = true) -> Single<T> {
return flatMap { response in
return Single.just(try response.mapModel(T.self, handleErr))
}
}
}
最後在專案中的網路請求時這樣發起的:
let bag = DisposeBag()
let provider = MoyaProvider<GitHub.GetUserProfile>()
provider.rx.request(GitHub.GetUserProfile(name: "yoxisem544"))
.filterSuccessfulStatusCodes()
.map(Profile.self)
.subscribe(onSuccess: { p in
print(p)
}, onError: { e in
print(e.localizedDescription)
})
.disposed(by: bag)