最近要做新项目,原来的项目框架老了也该升级了。了解了下Moya,发现异常的好用。很好的把接口,接口所需参数,网络请求时的自定义设置等集中到了一起。修改和增加都非常清晰。
导入就不说了,贴上Github网址——Moya
Moya的基本思想
我们希望一些网络抽象层能够直接充分地封装实际调用Alamofire。这应该是简单的,常见的事情是容易的,但足够全面,复杂的事情也很容易。
如果你使用Alamofire来抽象出来URLSession,为什么不用东西去抽象出URL,参数等等呢?
Moya的一些很棒的功能:
- 编译时检查正确的API端点访问。
- 让您定义具有相关枚举值的不同端点并且清晰的使用。
- 将测试桩作为一流公民,使单元测试非常简单。
使用Moya
首先创建一个枚举来存放你所有的API
//API 参数列表
enum bsAPI {
case getRequest
case Login(phoneNumber: String, password: String)
case testNetwork
}
对你的枚举(bsAPI)进行扩展,并遵循TargetType协议。这里面会把请求的网址,路径参数,基本参数,请求类型等等全部定义好。
extension bsAPI: TargetType {
//网址
var baseURL: URL{
return URL.init(string: Using_URL)!
}
//内容拼接
var path:String{
return ""
}
var method: Moya.Method{
switch self {
case .getRequest:
return .get
default:
return .post
}
}
var parameters: [String: Any]?{
switch self {
case .Login(let phoneNumber, let password):
let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5()) /// 这是我的加密方法,自己Demo就随便一个字典就好
return ["func":"", "words":words]
case .testNetwork:
let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5()) /// 这是我的加密方法,自己Demo就随便一个字典就好
return ["words":words]
default:
return nil
}
}
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// Provides stub data for use in testing.
var sampleData: Data {
return "".data(using: .utf8)!
}
/// The type of HTTP task to be performed.
var task: Task {
return .request
}
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool {
return false
}
}
协议定义好了之后使用就异常简单, 定义一个类型就可以开始使用了。
let provider = MoyaProvider<bsAPI>()
provider.request(.Show) { result in
// do something with result
}
但是这是最基础的用法,MoyaProvider是提供很多属性的,下面的写法就为你的网络请求添加了请求时状态栏转圈(状态栏菊花加载)和网络请求页面菊花加载。这样就不用每个请求的时候都带上MBProgressHUD的使用了。
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) {
self.endpointClosure = endpointClosure
self.requestClosure = requestClosure
self.stubClosure = stubClosure
self.manager = manager
self.plugins = plugins
self.trackInflights = trackInflights
}
这是MoyaProvider的默认初始化方法,我们会对requestClosure和plugins这两个属性作出自定义。
requestClosure的自定义
let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
guard var request = endpoint.urlRequest else { return }
request.timeoutInterval = 10 //设置请求超时时间
done(.success(request))
}
plugins的自定义
let networkPlugin = NetworkActivityPlugin { (type) in
switch type {
case .began:
/// 状态栏转圈
UIApplication.shared.isNetworkActivityIndicatorVisible = true
case .ended:
/// 状态栏停止转圈
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
这里用到的是Moya的网络监控插件,NetworkActivityPlugin,继承自PluginType。定义了状态栏的网络加载。下面要自定义一个继承自PluginType,网络请求时页面加载的插件。
class RequestLoadingPlugin: PluginType {
var HUD:MBProgressHUD = MBProgressHUD.init()
func willSend(_ request: RequestType, target: TargetType) {
print("开始请求")
if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
HUD.mode = MBProgressHUDMode.indeterminate
HUD.bezelView.color = UIColor.lightGray
HUD.removeFromSuperViewOnHide = true
HUD.backgroundView.style = .solidColor
HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
}
}
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
print("结束请求")
HUD.hide(animated: true, afterDelay: 0)
guard case Result.failure(_) = result else {
return
}
/// 请求失败
let errorReason: String = (result.error?.errorDescription)!
print("请求失败: \(errorReason)")
/// 没网 "The Internet connection appears to be offline."
/// 连接不到服务器 "Could not connect to the server."
var tip = "请求失败!"
if errorReason.contains("The Internet connection appears to be offline") {
tip = "网络不给力,请检查您的网络"
}
if errorReason.contains("Could not connect to the server") {
tip = "无法连接服务器"
}
/// 使用tip文字 进行提示
}
}
然后初始化MoyaProvider的时候带上自定义的插件
let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])
大功告成!!!
然后去简单使用下
bsProvider.request(bsAPI.testNetwork) { (result) in
switch result{
case let .success(res):
print("======\(res)======")
let responseDict = JSON(res.data) ///这里是导入了SwiftyJSON
print(responseDict)
case let .failure(error):
print("=====\(error)=========")
}
}
我是把API的定义和Manager分开了,在bsNetworkAPI文件里写测试地址/正式地址,接口名。这样子要找接口,改接口一目了然。
bsNetworkAPI文件代码大致如下
import UIKit
#if DEBUG
let Using_URL = "" //测试地址
#else
let Using_URL = "" //正式地址
#endif
/// 登陆
let FUNC_Login = "login"
/// 测试
let FUNC_Test = "test"
bsNetworkManager里的代码就是上文里面拆开讲的那部分,给大家一个合集,这样更方便学习。我就比较喜欢全部的代码都给我,然后我在去自己拆解来看。
import UIKit
import Moya
import Result
import MBProgressHUD
import SwiftyJSON
//API 参数列表
enum bsAPI {
case getRequest
case Login(phoneNumber: String, password: String)
case testNetwork
}
let networkPlugin = NetworkActivityPlugin { (type) in
switch type {
case .began:
/// 状态栏转圈
UIApplication.shared.isNetworkActivityIndicatorVisible = true
case .ended:
/// 状态栏停止转圈
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
guard var request = endpoint.urlRequest else { return }
request.timeoutInterval = 10 //设置请求超时时间
done(.success(request))
}
class RequestLoadingPlugin: PluginType {
var HUD:MBProgressHUD = MBProgressHUD.init()
func willSend(_ request: RequestType, target: TargetType) {
print("开始请求")
if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
HUD.mode = MBProgressHUDMode.indeterminate
HUD.bezelView.color = UIColor.lightGray
HUD.removeFromSuperViewOnHide = true
HUD.backgroundView.style = .solidColor
HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
}
}
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
print("结束请求")
HUD.hide(animated: true, afterDelay: 0)
/// 请求失败
let errorReason: String = (result.error?.errorDescription)!
print("请求失败: \(errorReason)")
/// 没网 "The Internet connection appears to be offline."
/// 连接不到服务器 "Could not connect to the server."
var tip = "请求失败!"
if errorReason.contains("The Internet connection appears to be offline") {
tip = "网络不给力,请检查您的网络"
}
if errorReason.contains("Could not connect to the server") {
tip = "无法连接服务器"
}
/// 使用tip文字 进行提示
}
}
let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])
extension bsAPI: TargetType {
//网址
var baseURL: URL{
return URL.init(string: Using_URL)!
}
//内容拼接
var path:String{
return FUNC_Test
}
var method: Moya.Method{
switch self {
case .getRequest:
return .get
default:
return .post
}
}
var parameters: [String: Any]?{
switch self {
case .Login(let phoneNumber, let password):
let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5())
return ["func":"", "words":words]
case .testNetwork:
let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5())
return ["words":words]
default:
return nil
}
}
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// Provides stub data for use in testing.
var sampleData: Data {
return "".data(using: .utf8)!
}
/// The type of HTTP task to be performed.
var task: Task {
return .request
}
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool {
return false
}
}
还有疑问可以去Github看看官方教程或下载官方例子——Moya
如果有对MBProgressHUD的那部分有疑问的,可以去iOS版 - MBProgressHUD设置背景方框为透明