Moya
使用 Moya 作为项目中的网络层有段时间了,一般在Swift项目中,我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层,方便我们的使用。但是在实际的项目中我们又会对Moya进行封装方便项目的使用,下面具体说说实际项目中Moya是怎么使用的。
先附上项目地址: 项目地址
Moya 优势
1. 编译时检查正确的API端点访问.
2. 使你定义不同端点枚举值对应相应的用途更加明晰.
3. 提高测试地位从而使单元测试更加容易.
本文主要介绍以下内容
1. Moya的用法
2. 设置请求头部信息
3. 设置超时时间
4. 自定义插件
5. 统一添加 loading
6. 网络层缓存
7. 打印请求参数及其返回数据
8. 自签名证书
一. Moya的具体用法以及代码实现
// 首页接口
let HomeProvider = MoyaProvider()
enum HomeGoodsAPI {
case homeGoodsList // 首页上面列表
case homePageBelowConten // 首页下面列表
case goodDetail(goodId: String) // 商品详情
case goodCategory //商品类别信息
case categoryGoodsList(categoryId: String,CategorySubId: String,page: Int) //商品列表
}
// 遵循 TargetType 代理 实现方法
extension HomeGoodsAPI: TargetType {
//服务器地址
public var baseURL: URL {
return URL(string: "https://wxmini.baixingliangfan.cn/baixing/")!
}
var path: String {
switch self {
case .homeGoodsList: return "wxmini/homePageContent"
case .homePageBelowConten: return "wxmini/homePageBelowConten"
case .goodDetail: return "wxmini/getGoodDetailById"
case .goodCategory: return "wxmini/getCategory"
case .categoryGoodsList: return "wxmini/getMallGoods"
}
}
var method: Moya.Method {
return .post
}
var task: Task {
var parmeters:[String:Any] = [:]
switch self {
case .homeGoodsList:
parmeters = ["lon":"116.47118377685547","lat":"39.91233444213867"]
case .homePageBelowConten:
parmeters = ["page":"1"]
case .goodDetail(let goodId):
parmeters = ["goodId":goodId]
case .goodCategory:
parmeters = ["":""]
case .categoryGoodsList(let categoryId,let CategorySubId,let page):
parmeters = ["categoryId":categoryId,"categorySubId":CategorySubId,"page":page]
}
return .requestParameters(parameters: parmeters, encoding: URLEncoding.default)
}
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
var headers: [String : String]? { return nil }
}
1. 初始化 HomeProvider 对象,将创建的枚举 HomeGoodsAPI 传入。
2. 在HomeGoodsAPI 中设置接口名,需要传入参数的在枚举中设置参数值。
3. 遵循 TargetType 代理 实现方法,设置 baseURL 即项目主域名。
4. 设置path 即 接口的方法名。
5. 设置请求参数 Task 。
在VC中调用 result 即 网络返回
HomeProvider.request(.goodCategory) { (result) in
switch result {
case .success(_):
break
case .failure(_):
break
@unknown default:
break
}
}
在OC中大家习惯的将项目中的网络请求统一封装一个公共类,在里面设置超时时间,设置请求头,设置加载控件,设置网络缓存,设置返回到VC中的数据格式,JSON或者data 等等,新建一个NetworkManager 类,用来专门处理网络请求。
二.设置 请求头 公共参数
// 网络请求的基本设置,这里可以拿到是具体的哪个网络请求,可以在这里做一些设置
private let myEndpointClosure = { (target: API) -> Endpoint in
/// 这里把endpoint重新构造一遍主要为了解决网络请求地址里面含有? 时无法解析的bug
let url = target.baseURL.absoluteString + target.path
var task = target.task
/*
如果需要在每个请求中都添加类似token参数的参数请取消注释下面代码
*/
// let additionalParameters = ["token":"888888"]
// let defaultEncoding = URLEncoding.default
// switch target.task {
// ///在你需要添加的请求方式中做修改就行,不用的case 可以删掉。。
// case .requestPlain:
// task = .requestParameters(parameters: additionalParameters, encoding: defaultEncoding)
// case .requestParameters(var parameters, let encoding):
// additionalParameters.forEach { parameters[$0.key] = $0.value }
// task = .requestParameters(parameters: parameters, encoding: encoding)
// default:
// break
// }
/*
如果需要在每个请求中都添加类似token参数的参数请取消注释上面代码
*/
var endpoint = Endpoint(
url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: task,
httpHeaderFields: target.headers
)
requestTimeOut = 30 // 每次请求都会调用endpointClosure 到这里设置超时时长 也可单独每个接口设置
switch target {
case .homeGoodsList:
return endpoint
case .homePageBelowConten:
requestTimeOut = 5
return endpoint
default:
return endpoint
}
}
三. 设置请求时长,打印请求参数和数据返回
/// 网络请求的设置
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
// 设置请求时长
request.timeoutInterval = requestTimeOut
// 打印请求参数
if let requestData = request.httpBody {
print("\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
parmeterStr = String(data: request.httpBody!, encoding: String.Encoding.utf8)!
// [URL absoluteString];
} else {
print("\(request.url!)" + "\(String(describing: request.httpMethod))")
}
done(.success(request))
} catch {
done(.failure(MoyaError.underlying(error, nil)))
}
}
四.设置加载loading 使用 NetworkActivityPlugin 插件
// NetworkActivityPlugin插件用来监听网络请求,界面上做相应的展示
private let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
print("networkPlugin \(changeType)")
// targetType 是当前请求的基本信息
switch changeType {
case .began:
print("开始请求网络")
SVProgressHUD .setDefaultMaskType(SVProgressHUDMaskType.clear)
SVProgressHUD .setBackgroundLayerColor(UIColor .blue)
SVProgressHUD .setDefaultStyle(SVProgressHUDStyle.light)
SVProgressHUD .setForegroundColor(MainColor)
SVProgressHUD .setDefaultAnimationType(SVProgressHUDAnimationType.flat)
SVProgressHUD .show(withStatus: "加载中")
SVProgressHUD .setMinimumDismissTimeInterval(20.0)
case .ended:
print("结束")
SVProgressHUD .dismiss()
}
}
五. 主网络请求方法封装以及数据缓存
// target HomeProvider 对象
/// isCarch 是否需要缓存,默认false
/// carchID 缓存参数
func NetWorkRequest(_ target: API, isCarch: Bool = false, carchID: NSString = "", completion: @escaping successCallback, failed: failedCallback?, errorResult: errorCallback?) -> Cancellable? {
// 先判断网络是否有链接 没有的话直接返回--代码略
/*
if !UIDevice.isNetworkConnect {
print("提示用户网络似乎出现了问题")
return nil
}
*/
/// 缓存代码 设置缓存路径
let pathcaches = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
let cachesDir = pathcaches[0]
let mutableSting = target.baseURL.absoluteString + target.path + (carchID as String)
let lastStr = mutableSting.replacingOccurrences(of: "/", with: "-")
let disPath = cachesDir + "/" + lastStr + "-.text"
if isCarch == true {
DispatchQueue.global().async {
do {
/// 获取json字符串
let str = try String .init(contentsOfFile: disPath, encoding: String.Encoding.utf8)
DispatchQueue.main.async {
/// 字符串转化为data
let data = str .data(using: String.Encoding.utf8, allowLossyConversion: true)
completion(data! as NSData)
}
} catch {
print(error)
}
}
}
return Provider.request(target) { result in
// 隐藏hud
switch result {
case let .success(response):
do {
let jsonData = try JSON(data: response.data)
print(jsonData)
if isCarch == true {
// 缓存
let jsonStr = String(data: response.data, encoding: String.Encoding.utf8) ?? ""
DispatchQueue.global().async {
do {
try jsonStr .write(toFile: disPath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
}
}
}
/// 这里的completion和failed判断条件依据不同项目来做,为演示demo我把判断条件注释了,直接返回completion。
completion(response.data as NSData)
print("flag不为1000 HUD显示后台返回message" + "\(jsonData[RESULT_MESSAGE].stringValue)")
if jsonData[RESULT_CODE].stringValue == "1000"{
completion(response.data as NSData)
}else{
// failed?(String(data: try! response.mapJSON() as! Data, encoding: String.Encoding.utf8)!)
}
} catch {}
case let .failure(error):
// 网络连接失败,提示用户
print("网络连接失败\(error)")
errorResult?()
}
}
}
调用
NetWorkRequest(.homePageBelowConten(parameters: ["page":self.page]),isCarch: true,carchID: "page-\(page)" as NSString, completion: { (responseString) -> (Void) in
// 轮播图数据
let json = JSON(responseString
// 数据处理刷新UI
}, failed: { (failedResutl) -> (Void) in
print("服务器返回code不为0000啦~\(failedResutl)")
}, errorResult: { () -> (Void) in
print("网络异常")
})
首先说一下这个函数的几个参数:
1. _target HomeProvider 对象,就是将Provider传入。
2.isCarch 是否需要缓存,默认false,因为一个App一般情况下并不需要全部页面缓存,只是首页或者个别页面需要缓存来提高用户体验,当不需要缓存时这个参数不用处理,需要时传true就ok
carchID 缓存参数 这个参数主要是为了区分相同的接口不同的参数去做缓存时存在不同的路径下面,从上面的方法可以看到缓存具体的思路就是 以 target.baseURL.absoluteString + target.path 为主路径将数据缓存到沙盒,当网络请求时如果沙盒中有数据开辟线程先将沙盒中的缓存取出来,回调到VC,展示UI。等网络请求结束再将网络端的数据回调,这样的思路。那么在缓存时就要主要,同样的接口如果参数不一样时那么就需要传这个carchID 了。比如此项目中,请求商品详情数据,每个商品的goodId是不一样的,如果有需求说缓存一下商品详情,那个肯定是一个商品一个缓存路径对应着一个缓存文件,所以必须穿一个carchID 。
3.回调:我直接回掉的response.data,当然也可以在网络层将数据转化成model回调给VC这个可以随意修改了
写到这里基本上就可以满足项目的需求了,如有遗漏,还请指教。
代码地址
关于swift的更多知识
请点击 swift文集!