在Swift中我们发送网络请求一般都是使用一个第三方库Alamofire
,OC中使用的AFNetworking
,设置好URL和parameter然后发送网络请求,就像下面这样:
let param = ["iid": iid]
let url = baseURL + articlePath
Alamofire.request(url, parameters: param).responseJSON { (response) in
switch response.result {
case .success(let value):
....
case .failure(let error):
}
}
这些代码一般都是写在项目的Service或者ViewModel文件中,随着项目的增大每一个Service文件或者ViewModel文件中就会有很多不同的网络请求,每一次网络请求都不免会写这样的代码,那么项目中的网络请求就会变得很乱。这篇文章同步发布在我的博客中
那么这时候一般我们会在项目中添加一个网络请求层,来管理网络请求,一般会叫APIManager
或者NetworkModel
,但是这样子还是会有一点不好:
- 这一层比较混乱,不好管理,混合了各种请求
- 不好做单元测试
但是Moya是专业处理这些问题而生滴。Moya有以下优点:
- 定义了一个清晰的网络结构
- 更加简单地进行网络单元测试
Moya是作用在Alamofire之上,让我们不再直接去使用Alamofire了,Moya也就可以看做我们的网络管理层,只不过他拥有更好更清晰的网络管理。可以看到下图,我们的APP直接操作Moya,让Moya去管理请求,不在跟Alamofire进行接触
网络请求
我们要怎么使用Moya进行请求呢?很简单。(参考官方Demo)比如我们要查询github上一个人的userProfile和一个人的repository。大伙可以想一下,我们如果直接使用Alamfire怎么去请求的。然而我们看下Moya怎么做:
首先我们需要声明一个enum来对请求进行明确分类。
public enum GitHub {
case userProfile(String) //请求profile
case userRepositories(String) //请求repository
}
然后我们需要让这个enum实现一个协议TargetType,点进去可以看到TargetType定义了我们发送一个网络请求所需要的东西,什么baseURL,parameter,method等一些计算性属性,我们要做的就是去实现这些东西,当然有带默认值的我们可以不去实现。相信下面代码中的东西大家都能看的懂,下面定义了每一个请求所需要的基本数据。
extension GitHub: TargetType {
public var baseURL: URL { return URL(string: "https://api.github.com")! }
public var path: String {
switch self {
case .userProfile(let name):
return "/users/\(name.urlEscaped)"
case .userRepositories(let name):
return "/users/\(name.urlEscaped)/repos"
}
}
public var method: Moya.Method {
return .get
}
public var parameters: [String: Any]? {
switch self {
case .userRepositories(_):
return ["sort": "pushed"]
default:
return nil
}
}
public var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
public var task: Task {
return .request
}
public var validate: Bool {
switch self {
return false
}
}
//这个就是做单元测试模拟的数据,必须要实现,只在单元测试文件中有作用
public var sampleData: Data {
switch self {
case .userProfile(let name):
return "[{\"name\": \"Repo Name\"}]".data(using: String.Encoding.utf8)!
case .userRepositories(_):
return ....
}
}
}
当我们实现了这个这个enum,我们就相当于完成了一个网络请求所需要的一个endpoint
,endpoint其实就是一个结构,包含了我们一个请求所需要的基本信息,比如url,parameter等。endpoint好了这时候我们就需要一个工具去发送这个请求。于是Moya提供一个发送请求的Provider
let provider = MoyaProvider()
// 使用我们的provider进行网络请求,请求某个人的仓库
provider.request(.userRepositories("codertian")) { result in
switch result {
case let .success(response):
.......
case let .failure(error):
......
}
}
当然上面的Provider的init方法有很多参数,但是都有默认值。
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
manager: Manager = MoyaProvider.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {
....
}
你可以点击进去看下每一个默认值都做了些啥,当然我们也可以自己去实现这些参数,然后init的时候把自定义参数传递进去。比如我们自定义一个endpointClouse
,然后创建Provider的时候传递进去就行了。
let endpointClosure = { (target: GitHub) -> Endpoint in
return Endpoint(url: url(target), sampleResponseClosure:{.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
}
let GitHubProvider = MoyaProvider(endpointClosure: endpointClosure)
RxSwift
Moya也有自己的RxSwift的扩展,不懂RxSwift的童鞋可以看下我们博客中的关于RxSwift库介绍的文章。Moya使用RxSwift很简单,如下所示我们只需要对请求结果进行监听就行了
let provider = RxMoyaProvider()//要使用RxMoyaProvider创建provider
provider.request(.zen).subscribe { event in
switch event {
case .next(let response):
// do something with the data
case .error(let error):
// handle the error
}
}
我们还可以对Observable进行扩展,自定义一些自己流水线操作,比如当捕获到错误的时候弹出一个toast,定义如下。
extension Observable {
func showErrorToast() -> Observable {
return self.doOn { event in
switch event {
case .error(let e):
...
}
}
}
}
那么使用的时候我们就可以使用这种方式调用
provider.request(.resetPassword(email: textField.text!))
.filterSuccessfulStatusCodes()
.showErrorToast() //捕获到错误就会自动弹出
.subscribeNext { response in
.....
}
Moya也为我们提供了很多Observable的扩展,让我们能更轻松的处理MoyaResponse,常用的如下:
- filter(statusCodes:) 过滤response状态码
- filterSuccessfulStatusCodes() 过滤状态码为请求成功的
- mapJSON() 将请求response转化为JSON格式
- mapString() 将请求response转化为String格式
具体可以参考官方文档
paramter
设置请求参数的时候有一点需要注意。参数设置要使用以下方式
public var parameters: [String: Any]? {
switch self {
case .users(let limit):
var params: [String: Any] = [:]
params["limit"] = limit
return params
.......
}
不能使用下面这种方式,因为上面的limit有可能为nil,这样子下面这个字典就会报错了。
public var parameters: [String: Any]? {
switch self {
case .users(let limit):
let params: [String: Any] = ["limit": limit]
return params
.......
}
插件
Moya在初始化Provider的时候可以传入一些插件,Moya库中默认有4个插件。
- AccessTokenPlugin 管理AccessToken的插件
- CredentialsPlugin 管理认证的插件
- NetworkActivityPlugin 管理网络状态的插件
- NetworkLoggerPlugin 管理网络log的插件
我们也可以自定义自己的插件,比如我们官方例子中当请求失败的时候弹出一个alert
final class RequestAlertPlugin: PluginType {
private let viewController: UIViewController
init(viewController: UIViewController) {
self.viewController = viewController
}
func willSend(request: RequestType, target: TargetType) {
........//实现发送请求前需要做的事情
}
func didReceive(result: Result, target: TargetType) {
guard case Result.failure(_) = result else { return }//只监听失败
// 弹出Alert
let alertViewController = UIAlertController(title: "Error", message: "Request failed with status code: \(error.response?.statusCode ?? 0)", preferredStyle: .alert)
alertViewController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
viewController.present(viewControllerToPresent: alertViewController, animated: true)
}
}
实现PluginType协议,然后再实现两个请求监听方法willSend和didReceive方法。在其中做某些事情就行了。
好了这里差不多包含了主要的使用方式,其他小情况请大伙参考官方文档吧!
小伙伴们如果感觉文章可以,可以关注博主博客
小伙伴们多多关注博主微博,探索博主内心世界
如要转载请注明出处。