在移动端的开发中,网络请求是必不可少的。之前写过Alamofire的简单使用,但是一般开发中都会对这些第三库封装,然后使用,之前自己封装的demo也是借鉴了一些Moya的设计思路。今天就介绍一下Moya一个帮助你处理网络层的第三方框架。
Moya是一个帮助我们管理Alamofire的网络管理层,可以让我们去更清晰的去管理我们的网络请求。
Moya本身支持多种集成,本文使用CocoaPods集成。Moya本身就支持了对RxSwift的扩展,所以我们只需要在Podfile里添加一下:
pod 'Moya', '~> 10.0'
# or
pod 'Moya/RxSwift', '~> 10.0'
然后执行 pod install
在使用的文件中,引入即可:import Moya
定义枚举,存储网络请求
// 1 定义一个枚举,存放我们的网络请求
enum ApiManager {
case login(username:String,password:String,token:String)
}
实现moya的TargetType协议
extension ApiManager:TargetType{
var baseURL: URL {
return URL.init(string: BaseURL)!
}
//请求路径
var path:String{
switch self {
case .login(username: _, password:_ , token:_):
return "login/accountLogin"
}
}
var headers: [String: String]? {
return nil;
}
//请求的参数
var parameters: [String: Any]? {
switch self {
case .login(username: let userName, password: let pwd, token: let token):
return ["account":userName,"pwd":pwd,"deviceToken":token];
}
}
///请求方式
var method: Moya.Method {
switch self {
case .login(username: _, password: _, token: _):
return .post;
}
}
/// The method used for parameter encoding.
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// Provides stub data for use in testing.
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
//MARK:task type
var task: Task {
return .requestPlain
}
var validate: Bool {
return false
}
}
发起网络请求
let provider = MoyaProvider();
provider.request(.login(username: "haha", password: "123456", token: "qwe")) { (result) in
if result.error == nil{
LLog(result.value);
}
}
RxSwift是一个可以帮助我们简化异步编程的框架,类似于OC的RxCocoa、Java的RXJava、JS的rxjs、kotlin的RxKotlin等,属于Rx家族的一员。学习曲线,相对陡峭。适用于MVVM框架,身边的开发者,对其也是褒贬不一。个人感觉还是一个值得开发者去学习、使用的框架。这边文章就不详细介绍,具体的可移步查看RXSwift的中文文档。
阿里开源的一个数据序列化的框架,功能类似于OC的MJExtension.个人感觉在Swift中HandyJSON和SwiftyJSON都是比较好用的数据处理框架。在这里就不做过多的介绍,具体的使用您可以查看HandyJSON的中文文档
参见上文的Moya简繁使用
在这里我们使用HanyJSON处理接口返回数据,因为我们使用RxSwift,在处理网络请求,所有我们是对ObservableType 数据类型 扩展mapModel。
extension ObservableType where E == Response {
public func mapModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
return flatMap { response -> Observable<T> in
return Observable.just(response.mapModel(T.self))
}
}
}
extension Response {
func mapModel<T: HandyJSON>(_ type: T.Type) -> T {
let jsonString = String.init(data: data, encoding: .utf8)
return JSONDeserializer<T>.deserializeFrom(json: jsonString)!
}
}
使用provider异步请求,并处理返回数据。
// 1、接口请求完成后, subscribe 会收到next和complete两个事件 当event.element不为空时即为next事件。
provider.rx.request(.login(username: "151xxxxxxxxxx", password: "123456", token: "")).asObservable().mapModel(LoginResponseModel .self).subscribe { (event) in
//当event.element 不为空时,返回的就是我们之前mapModel的类
if let model = event.element{
LLog(model.data.accessToken);
LLog(model.data.crowdToken);
}else{
LLog("当前事件:\(event)")
}
}.disposed(by: disposeBag);
//直接处理next事件,error和completed分开处理
provider.rx.request(.login(username: "15136176473", password: "123456", token: "")).asObservable().mapModel(LoginResponseModel .self).subscribe(onNext: { (model) in
LLog(model.data.crowdToken)
}, onError: { (error) in
LLog("请求出错")
}, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag);
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
callbackQueue: DispatchQueue? = nil,
manager: Manager = MoyaProvider.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {}
Moya是一个比较全面的好用的网络管理工具,在上面我们只是使用了部分功能。通过provider的初始化,我们可以发现在开发中我们还可以处理很多。比如:
Moya 的插件机制也很好用,提供了以下接口:
/// Called to modify a request before sending
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
/// Called immediately before a request is sent over the network (or stubbed).
func willSend(_ request: RequestType, target: TargetType)
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceive(_ result: Result, target: TargetType)
/// Called to modify a result before completion
func process(_ result: Result, target: TargetType) -> Result
与主功能接口脱离,降低了耦合度我们可以再这里处理请求的权限、token、loading等。 例如:
public final class RequestLoadingPlugin:PluginType{
public func willSend(_ request: RequestType, target: TargetType) {
LLog("接口开始请求")
self.showHUD()
}
public func didReceive(_ result: Result, target: TargetType) {
LLog("接口请求完成")
self.dismissHUD();
}
//MARK:-是否显示请求加载框
fileprivate func showHUD(_ isShow:Bool = true){
if(isShow){
SVProgressHUD.show();
}
}
//MARK:-隐藏请求加载框
fileprivate func dismissHUD(){
SVProgressHUD.dismiss();
}
}
在provider的初始化注册我们初始化的插件即可
let provider = MoyaProvider(plugins:[RequestLoadingPlugin()]);
public typealias EndpointClosure = (Target) -> Endpoint
public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
public typealias StubClosure = (Target) -> Moya.StubBehavior
public typealias RequestResultClosure = (Result) -> Void
从上面的三个闭包中,我们可以看出
EndpointClosure 是输入一个Target 返回一个Endpoint 也就是完成Target->Endpoint。
Endpoint的数据结构
open class Endpoint {
public typealias SampleResponseClosure = () -> EndpointSampleResponse
open let url: String
open let sampleResponseClosure: SampleResponseClosure
open let method: Moya.Method
open let task: Task
open let httpHeaderFields: [String: String]?
}
初始化的默认值是:
public final class func defaultEndpointMapping(for target: Target) -> Endpoint {
return Endpoint(
url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers
)
}
上面的代码只是创建并返回Endpoint实例。很多时候,我们可以自定义这个闭包来做更多额外的事情。比如我们处理一些网络状态码、或者结合stub 模拟网络数据等。
- RequestClosure 是输入一个Endpoint和RequestResultClosure闭包 返回Void
RequestClosure主要是实现Endpoint->NSURLRequest 用于发起真正的网络请求。
public typealias RequestResultClosure = (Result) -> Void
在输入中RequestResultClosure就是对接口请求完成后的返回。
初始化的默认值是:
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)))
}
}
这里就是处理最后的urlRequest。如果你想设置全局的urlRequest,这里也是最后的机会了。我们可以再这里修改请求的超时时间、缓存策略、cookie等。
示例:
let requestClosure = { (endpoint: Endpoint, closure: (Result) -> Void) -> Void in
do {
var urlRequest = try endpoint.urlRequest()
urlRequest.timeoutInterval = 30.0
urlRequest.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
urlRequest.httpShouldHandleCookies = false
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)))
}
}
示例
let provider = MoyaProvider(requestClosure:requestClosure,plugins:[RequestLoadingPlugin()]);
public typealias StubClosure = (Target) -> Moya.StubBehavior
public enum StubBehavior {
/// 不使用Stub来返回模拟的网络数据
case never
/// 立刻返回Stub的数据
case immediate
/// 在几秒后返回stub的数据
case delayed(seconds: TimeInterval)
}
示例:
编写测试数据
extension ApiManager:TargetType{
...
/// Provides stub data for use in testing.
var sampleData: Data {
switch self {
case .login(username: _, password: _, token: _):
return "{'code': 200,'Token':'123455'}".data(using: String.Encoding.utf8)!
default:
return "".data(using: String.Encoding.utf8)!
}
}
}
初始化EndpointClosure
let endPointAction = { (target: ApiManager) -> Endpoint in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
switch target {
case .login(username: _, password:_ , token:_):
return Endpoint(
url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) } ,
method: target.method,
task: target.task,
httpHeaderFields: target.headers
)}
}
初始化StubClosure
let stubAction: (_ type:ApiManager) -> Moya.StubBehavior = { type in
switch type {
case .login(username: _, password:_ , token:_):
return Moya.StubBehavior.delayed(seconds: 3.0)
}
}
初始化provider
let provider = MoyaProvider<ApiManager>(endpointClosure:endPointAction,requestClosure:_requestClosure,stubClosure:stubAction,plugins:[RequestLoadingPlugin()]);
Moya本身并不是直接处理网络请求的第三方库。它只是一个抽象的网络层,对真正发起网络请求的Manager进行管理:例如Alamofire
默认参数:
public final class func defaultAlamofireManager() -> Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
let manager = Manager(configuration: configuration)
manager.startRequestsImmediately = false
return manager
}
封装Alamofire,使Moya.Manager == Alamofire.SessionManager
public typealias Manager = Alamofire.SessionManager
internal typealias Request = Alamofire.Request
internal typealias DownloadRequest = Alamofire.DownloadRequest
internal typealias UploadRequest = Alamofire.UploadRequest
internal typealias DataRequest = Alamofire.DataRequest
所以,当我们想要定义自己的Manager,我们也可以传入自己的Manager到Moya的provider中。
示例
let policies: [String: ServerTrustPolicy] = [
"example.com": .PinPublicKeys(
publicKeys: ServerTrustPolicy.publicKeysInBundle(),
validateCertificateChain: true,
validateHost: true
)
]
let policies: [String: ServerTrustPolicy] = [
"example.com": .pinPublicKeys(
publicKeys: ServerTrustPolicy.publicKeys(),
validateCertificateChain: true,
validateHost: true
)
]
let manager:Manager = Manager(configuration: URLSessionConfiguration.default, delegate: SessionDelegate(), serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
let provider = MoyaProvider(manager: manager)
以上,基本覆盖了Moya开发中API使用示例以及代码,总的来说Moya还是一个很不错的网络管理工具。