田腾飞的博客

【iOS开发】Moya入坑记-用法解读篇

在Swift中我们发送网络请求一般都是使用一个第三方库Alamofire,OC中使用的是AFNetworking,设置好URL和parameter然后发送网络请求,就像下面这样:

1
2
3
4
5
6
7
8
9
10
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来对请求进行明确分类。

1
2
3
4
public enum GitHub {
case userProfile(String) //请求profile
case userRepositories(String) //请求repository
}

然后我们需要让这个enum实现一个协议TargetType,点进去可以看到TargetType定义了我们发送一个网络请求所需要的东西,什么baseURL,parameter,method等一些计算性属性,我们要做的就是去实现这些东西,当然有带默认值的我们可以不去实现。相信下面代码中的东西大家都能看的懂,下面定义了每一个请求所需要的基本数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

1
2
3
4
5
6
7
8
9
10
11
let provider = MoyaProvider<GitHub>()
// 使用我们的provider进行网络请求,请求某个人的仓库
provider.request(.userRepositories("codertian")) { result in
switch result {
case let .success(response):
.......
case let .failure(error):
......
}
}

当然上面的Provider的init方法有很多参数,但是都有默认值。

1
2
3
4
5
6
7
8
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {
....
}

你可以点击进去看下每一个默认值都做了些啥,当然我们也可以自己去实现这些参数,然后init的时候把自定义参数传递进去。比如我们自定义一个endpointClouse,然后创建Provider的时候传递进去就行了。

1
2
3
4
5
let endpointClosure = { (target: GitHub) -> Endpoint<GitHub> in
return Endpoint<GitHub>(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很简单,如下所示我们只需要对请求结果进行监听就行了

1
2
3
4
5
6
7
8
9
let provider = RxMoyaProvider<NewsService>()//要使用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,定义如下。

1
2
3
4
5
6
7
8
9
10
extension Observable {
func showErrorToast() -> Observable<Element> {
return self.doOn { event in
switch event {
case .error(let e):
...
}
}
}
}

那么使用的时候我们就可以使用这种方式调用

1
2
3
4
5
6
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

设置请求参数的时候有一点需要注意。参数设置要使用以下方式

1
2
3
4
5
6
7
8
public var parameters: [String: Any]? {
switch self {
case .users(let limit):
var params: [String: Any] = [:]
params["limit"] = limit
return params
.......
}

不能使用下面这种方式,因为上面的limit有可能为nil,这样子下面这个字典就会报错了。

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final class RequestAlertPlugin: PluginType {
private let viewController: UIViewController
init(viewController: UIViewController) {
self.viewController = viewController
}
func willSend(request: RequestType, target: TargetType) {
........//实现发送请求前需要做的事情
}
func didReceive(result: Result<Response, MoyaError>, 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方法。在其中做某些事情就行了。

好了这里差不多包含了主要的使用方式,其他小情况请大伙参考官方文档吧!🤒

小伙伴们如果感觉文章可以,可以关注博主博客
小伙伴们多多关注博主微博,探索博主内心世界😁
如要转载请注明出处。

热评文章