Moya
使用 Moya 作為項(xiàng)目中的網(wǎng)絡(luò)層有段時(shí)間了,一般在Swift項(xiàng)目中丐重,我們用Alamofire來做網(wǎng)絡(luò)庫(kù).而Moya在Alamofire的基礎(chǔ)上又封裝了一層,方便我們的使用腔召。但是在實(shí)際的項(xiàng)目中我們又會(huì)對(duì)Moya進(jìn)行封裝方便項(xiàng)目的使用,下面具體說說實(shí)際項(xiàng)目中Moya是怎么使用的扮惦。
先附上項(xiàng)目地址: 項(xiàng)目地址
Moya 優(yōu)勢(shì)
1. 編譯時(shí)檢查正確的API端點(diǎn)訪問.
2. 使你定義不同端點(diǎn)枚舉值對(duì)應(yīng)相應(yīng)的用途更加明晰.
3. 提高測(cè)試地位從而使單元測(cè)試更加容易.
本文主要介紹以下內(nèi)容
1. Moya的用法
2. 設(shè)置請(qǐng)求頭部信息
3. 設(shè)置超時(shí)時(shí)間
4. 自定義插件
5. 統(tǒng)一添加 loading
6. 網(wǎng)絡(luò)層緩存
7. 打印請(qǐng)求參數(shù)及其返回?cái)?shù)據(jù)
8. 自簽名證書
一. Moya的具體用法以及代碼實(shí)現(xiàn)
// 首頁(yè)接口
let HomeProvider = MoyaProvider<HomeGoodsAPI>()
enum HomeGoodsAPI {
case homeGoodsList // 首頁(yè)上面列表
case homePageBelowConten // 首頁(yè)下面列表
case goodDetail(goodId: String) // 商品詳情
case goodCategory //商品類別信息
case categoryGoodsList(categoryId: String,CategorySubId: String,page: Int) //商品列表
}
// 遵循 TargetType 代理 實(shí)現(xiàn)方法
extension HomeGoodsAPI: TargetType {
//服務(wù)器地址
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 對(duì)象臀蛛,將創(chuàng)建的枚舉 HomeGoodsAPI 傳入。
2. 在HomeGoodsAPI 中設(shè)置接口名崖蜜,需要傳入?yún)?shù)的在枚舉中設(shè)置參數(shù)值浊仆。
3. 遵循 TargetType 代理 實(shí)現(xiàn)方法,設(shè)置 baseURL 即項(xiàng)目主域名豫领。
4. 設(shè)置path 即 接口的方法名抡柿。
5. 設(shè)置請(qǐng)求參數(shù) Task 。
在VC中調(diào)用 result 即 網(wǎng)絡(luò)返回
HomeProvider.request(.goodCategory) { (result) in
switch result {
case .success(_):
break
case .failure(_):
break
@unknown default:
break
}
}
在OC中大家習(xí)慣的將項(xiàng)目中的網(wǎng)絡(luò)請(qǐng)求統(tǒng)一封裝一個(gè)公共類等恐,在里面設(shè)置超時(shí)時(shí)間洲劣,設(shè)置請(qǐng)求頭,設(shè)置加載控件课蔬,設(shè)置網(wǎng)絡(luò)緩存囱稽,設(shè)置返回到VC中的數(shù)據(jù)格式,JSON或者data 等等二跋,新建一個(gè)NetworkManager 類战惊,用來專門處理網(wǎng)絡(luò)請(qǐng)求。
二.設(shè)置 請(qǐng)求頭 公共參數(shù)
// 網(wǎng)絡(luò)請(qǐng)求的基本設(shè)置,這里可以拿到是具體的哪個(gè)網(wǎng)絡(luò)請(qǐng)求扎即,可以在這里做一些設(shè)置
private let myEndpointClosure = { (target: API) -> Endpoint in
/// 這里把endpoint重新構(gòu)造一遍主要為了解決網(wǎng)絡(luò)請(qǐng)求地址里面含有? 時(shí)無法解析的bug
let url = target.baseURL.absoluteString + target.path
var task = target.task
/*
如果需要在每個(gè)請(qǐng)求中都添加類似token參數(shù)的參數(shù)請(qǐng)取消注釋下面代碼
*/
// let additionalParameters = ["token":"888888"]
// let defaultEncoding = URLEncoding.default
// switch target.task {
// ///在你需要添加的請(qǐng)求方式中做修改就行吞获,不用的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
// }
/*
如果需要在每個(gè)請(qǐng)求中都添加類似token參數(shù)的參數(shù)請(qǐng)取消注釋上面代碼
*/
var endpoint = Endpoint(
url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: task,
httpHeaderFields: target.headers
)
requestTimeOut = 30 // 每次請(qǐng)求都會(huì)調(diào)用endpointClosure 到這里設(shè)置超時(shí)時(shí)長(zhǎng) 也可單獨(dú)每個(gè)接口設(shè)置
switch target {
case .homeGoodsList:
return endpoint
case .homePageBelowConten:
requestTimeOut = 5
return endpoint
default:
return endpoint
}
}
三. 設(shè)置請(qǐng)求時(shí)長(zhǎng)衫哥,打印請(qǐng)求參數(shù)和數(shù)據(jù)返回
/// 網(wǎng)絡(luò)請(qǐng)求的設(shè)置
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
// 設(shè)置請(qǐng)求時(shí)長(zhǎng)
request.timeoutInterval = requestTimeOut
// 打印請(qǐng)求參數(shù)
if let requestData = request.httpBody {
print("\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "發(fā)送參數(shù)" + "\(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)))
}
}
四.設(shè)置加載loading 使用 NetworkActivityPlugin 插件
// NetworkActivityPlugin插件用來監(jiān)聽網(wǎng)絡(luò)請(qǐng)求,界面上做相應(yīng)的展示
private let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
print("networkPlugin \(changeType)")
// targetType 是當(dāng)前請(qǐng)求的基本信息
switch changeType {
case .began:
print("開始請(qǐng)求網(wǎng)絡(luò)")
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("結(jié)束")
SVProgressHUD .dismiss()
}
}
五. 主網(wǎng)絡(luò)請(qǐng)求方法封裝以及數(shù)據(jù)緩存
// target HomeProvider 對(duì)象
/// isCarch 是否需要緩存襟锐,默認(rèn)false
/// carchID 緩存參數(shù)
func NetWorkRequest(_ target: API, isCarch: Bool = false, carchID: NSString = "", completion: @escaping successCallback, failed: failedCallback?, errorResult: errorCallback?) -> Cancellable? {
// 先判斷網(wǎng)絡(luò)是否有鏈接 沒有的話直接返回--代碼略
/*
if !UIDevice.isNetworkConnect {
print("提示用戶網(wǎng)絡(luò)似乎出現(xiàn)了問題")
return nil
}
*/
/// 緩存代碼 設(shè)置緩存路徑
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 {
/// 字符串轉(zhuǎn)化為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判斷條件依據(jù)不同項(xiàng)目來做撤逢,為演示demo我把判斷條件注釋了,直接返回completion。
completion(response.data as NSData)
print("flag不為1000 HUD顯示后臺(tái)返回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):
// 網(wǎng)絡(luò)連接失敗蚊荣,提示用戶
print("網(wǎng)絡(luò)連接失敗\(error)")
errorResult?()
}
}
}
調(diào)用
NetWorkRequest(.homePageBelowConten(parameters: ["page":self.page]),isCarch: true,carchID: "page-\(page)" as NSString, completion: { (responseString) -> (Void) in
// 輪播圖數(shù)據(jù)
let json = JSON(responseString
// 數(shù)據(jù)處理刷新UI
}, failed: { (failedResutl) -> (Void) in
print("服務(wù)器返回code不為0000啦~\(failedResutl)")
}, errorResult: { () -> (Void) in
print("網(wǎng)絡(luò)異常")
})
首先說一下這個(gè)函數(shù)的幾個(gè)參數(shù):
1. _target HomeProvider 對(duì)象初狰,就是將Provider傳入。
2.isCarch 是否需要緩存互例,默認(rèn)false,因?yàn)橐粋€(gè)App一般情況下并不需要全部頁(yè)面緩存奢入,只是首頁(yè)或者個(gè)別頁(yè)面需要緩存來提高用戶體驗(yàn),當(dāng)不需要緩存時(shí)這個(gè)參數(shù)不用處理媳叨,需要時(shí)傳true就ok
carchID 緩存參數(shù) 這個(gè)參數(shù)主要是為了區(qū)分相同的接口不同的參數(shù)去做緩存時(shí)存在不同的路徑下面腥光,從上面的方法可以看到緩存具體的思路就是 以 target.baseURL.absoluteString + target.path 為主路徑將數(shù)據(jù)緩存到沙盒,當(dāng)網(wǎng)絡(luò)請(qǐng)求時(shí)如果沙盒中有數(shù)據(jù)開辟線程先將沙盒中的緩存取出來糊秆,回調(diào)到VC武福,展示UI。等網(wǎng)絡(luò)請(qǐng)求結(jié)束再將網(wǎng)絡(luò)端的數(shù)據(jù)回調(diào)痘番,這樣的思路捉片。那么在緩存時(shí)就要主要,同樣的接口如果參數(shù)不一樣時(shí)那么就需要傳這個(gè)carchID 了汞舱。比如此項(xiàng)目中伍纫,請(qǐng)求商品詳情數(shù)據(jù),每個(gè)商品的goodId是不一樣的昂芜,如果有需求說緩存一下商品詳情莹规,那個(gè)肯定是一個(gè)商品一個(gè)緩存路徑對(duì)應(yīng)著一個(gè)緩存文件,所以必須穿一個(gè)carchID 泌神。
3.回調(diào):我直接回掉的response.data访惜,當(dāng)然也可以在網(wǎng)絡(luò)層將數(shù)據(jù)轉(zhuǎn)化成model回調(diào)給VC這個(gè)可以隨意修改了