項(xiàng)目第一版網(wǎng)絡(luò)框架用的是siesta,它的緩存與自動(dòng)刷新確實(shí)很好用而且代碼很簡(jiǎn)潔,但是在文件的上傳與下載以及對(duì)返回類(lèi)型需要精確匹配要求這方面就很不友好,所以在第二版的我選擇了Moya,它是一個(gè)網(wǎng)絡(luò)抽象層,它在Alamofire基礎(chǔ)上提供了一系列的抽象接口方便維護(hù)。關(guān)于
Moya的使用介紹很多,我就不再贅述了。我主要記錄一下我在使用過(guò)程中學(xué)到的處理方式。我的網(wǎng)絡(luò)框架是搭著HandyJSON和RxSwift一起構(gòu)建的。
1 Moya
- 1 代碼
import Foundation
import enum Result.Result
import Alamofire
//設(shè)置請(qǐng)求超時(shí)時(shí)間
private let requestTimeoutClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<ApiManager>.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
request.timeoutInterval = 60
done(.success(request))
} catch {
return
}
}
let ApiManagerProvider = MoyaProvider<ApiManager>(endpointClosure: endpointMapping, requestClosure: requestTimeoutClosure, plugins:[])
// MARK: 取消所有請(qǐng)求
func cancelAllRequest() {
WTOtherProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach { $0.cancel() }
uploadTasks.forEach { $0.cancel() }
downloadTasks.forEach { $0.cancel() }
}
WTLoginProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach { $0.cancel() }
uploadTasks.forEach { $0.cancel() }
downloadTasks.forEach { $0.cancel() }
}
……
}
public func endpointMapping<Target: TargetType>(target: Target) -> Endpoint {
WTDLog("請(qǐng)求連接:\(target.baseURL)\(target.path) \n方法:\(target.method)\n參數(shù):\(String(describing: target.task.self)) ")
return MoyaProvider.defaultEndpointMapping(for: target)
}
final class RequestAlertPlugin: PluginType {
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
return request
}
func willSend(_ request: RequestType, target: TargetType) {
//實(shí)現(xiàn)發(fā)送請(qǐng)求前需要做的事情
}
public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
switch result {
case .success(let response):
guard response.statusCode == 200 else {
if response.statusCode == 401 {
if isJumpLogin == false {
cancelAllRequest()
// 退出登錄
if let nvc = (WTNavigationManger.Nav as? WTMainViewController) {
nvc.login()
}
}
}
return
}
var json = try? JSON(data: response.data)
WTDLog("請(qǐng)求狀態(tài)碼\(json?["status"] ?? "")")
guard let codeString = json?["status"] else {return}
if codeString == 401 {// 退出登錄
if isJumpLogin == false {
cancelAllRequest()
if let nvc = (WTNavigationManger.Nav as? WTMainViewController) {
nvc.login()
}
}
break
}
case .failure(let error):
WTDLog(error)
let myAppdelegate = UIApplication.shared.delegate as! AppDelegate
myAppdelegate.listenNetwork()
break
}
}
}
struct AuthPlugin: PluginType {
let token: String
}
enum ApiManager {
}
extension ApiManager: TargetType {
var headers: [String : String]? {
var dict = ["ColaLanguage": ("common.isChinese".L() == "YES") ? "CN" : "EN"]
if let authToken = WTLoginInfoManger.shareDataSingle.model?.accessToken {
dict["Authorization"] = authToken
}
return dict
}
var baseURL: URL {
return URL.init(string: AppURLHOST.MyPublicBaseURL)!
}
var path: String {
return ""
}
var method: Moya.Method {
return .get
}
var task: Task {
return .requestPlain
}
var validate: Bool {
return false
}
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
}
/// 數(shù)據(jù) 轉(zhuǎn) 模型
extension ObservableType where E == Response {
public func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
return flatMap { response -> Observable<T> in
return Observable.just(response.mapHandyJsonModel(T.self))
}
}
}
/// 數(shù)據(jù) 轉(zhuǎn) 模型
extension Response {
func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> T {
let jsonString = String.init(data: data, encoding: .utf8)
if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {
return modelT
}
return JSONDeserializer<T>.deserializeFrom(json: "{\"msg\":\"\("common.try".L())\"}")!
}
}
/// 自定義插件
public final class NetworkLoadingPlugin: PluginType {
public func willSend(_ request: RequestType, target: TargetType) {
}
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
}
}
-
2 模式Target -> Endpoint -> Request
來(lái)自GitHub圖片
Moya雖然是基于Alamofire的但是我們?cè)诖a中卻不會(huì)和Alamofire打交道,它是通過(guò)枚舉來(lái)管理API的。我在項(xiàng)目中定義來(lái)一個(gè)API基類(lèi),然后為每一個(gè)模塊定義了一個(gè)API管理類(lèi)。
enum HomeApiManager {
case getBanner // 獲取輪播
case getAnnouncement(per_page: String) // 獲取公告
}
對(duì)于請(qǐng)求類(lèi)型的改變和對(duì)于URL的改變也是通過(guò)枚舉
var method: Moya.Method {
switch self {
case .orderCreate:
return .post
case .orderCancelById, .orderCancelByPair:
return .delete
default:
return .get
}
}
var path: String {
switch self {
case .getKline:
return "/api/kline"
case .transGetByID(let orderId):
return "/api/\(orderId)"
}
}
請(qǐng)求任務(wù)
var task: Task {
switch self {
case .securityPostGoogleAuth(let tokenKey, let oldGoogleCode, let googleCode, let captcha):
return .requestParameters(parameters: ["captcha": captcha], encoding: JSONEncoding.default) // post請(qǐng)求
case .getReward(let type, let cursor, let limit):
return .requestParameters(parameters: ["type": type], encoding: URLEncoding.default) // 其它請(qǐng)求
case .uploadImage(let imageArry):
let formDataAry:NSMutableArray = NSMutableArray()
for (index,image) in imageArry.enumerated() {
//圖片轉(zhuǎn)成Data
let data:Data = image.jpegData(compressionQuality: 0.7)!
//根據(jù)當(dāng)前時(shí)間設(shè)置圖片上傳時(shí)候的名字
var dateStr: String = "yyyy-MM-dd-HH:mm:ss".timeStampToString(timeStamp: Date().timeIntervalSince1970)
//別忘記這里給名字加上圖片的后綴哦
dateStr = dateStr.appendingFormat("-%i.jpg", index)
let formData = MultipartFormData(provider: .data(data), name: "file\(index)", fileName: dateStr, mimeType: "image/jpeg")
formDataAry.add(formData)
}
return .uploadCompositeMultipart(formDataAry as! [MultipartFormData], urlParameters: [
:])
default:
return .requestPlain
}
}
- 3 插件機(jī)制
Moya的另一個(gè)強(qiáng)大的功能就是它的插件機(jī)制,提供了兩個(gè)接口,willSendRequest 和 didReceiveResponse,它可以在請(qǐng)求前和請(qǐng)求后做一些額外的操作而和主功能是解耦的,比如可以在請(qǐng)求前開(kāi)始加載動(dòng)畫(huà)請(qǐng)求結(jié)束后移除加載動(dòng)畫(huà),還可以自定義插件。
final class RequestAlertPlugin: PluginType {
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
return request
}
func willSend(_ request: RequestType, target: TargetType) {
現(xiàn)發(fā)送請(qǐng)求前需要做的事情
if target.headers?["isHiddentLoading"] != "true" {
currentView?.addSubview(activityIndicatorView)
activityIndicatorView.center = currentView!.center
activityIndicatorView.startAnimating()
}
}
public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
if activityIndicatorView.isAnimating {
activityIndicatorView.stopAnimating()
activityIndicatorView.removeFromSuperview()
}
}
}
/// 自定義插件
public final class NetworkLoadingPlugin: PluginType {
public func willSend(_ request: RequestType, target: TargetType) {
}
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
}
}
Moya默認(rèn)有4個(gè)插件
- AccessTokenPlugin // 管理AccessToken的插件
- CredentialsPlugin // 管理認(rèn)證的插件
- NetworkActivityPlugin // 管理網(wǎng)絡(luò)狀態(tài)的插件
- NetworkLoggerPlugin // 管理網(wǎng)絡(luò)log的插件
3 RxSwift
這里的RxSwift不是完整的RxSwift,而是為Moya定制的一個(gè)擴(kuò)展(pod 'Moya/RxSwift')在數(shù)據(jù)請(qǐng)求回來(lái)后進(jìn)行處理。
- request() 傳入API
- asObservable() 是Moya為RxSwift提供的擴(kuò)展方法,返回可監(jiān)聽(tīng)序列
- mapHandyJsonModel() 也是Moya RxSwift的擴(kuò)展方法進(jìn)行自定義的,可以把返回的數(shù)據(jù)解析成model
- subscribe() 是對(duì)處理過(guò)的 Observable 訂閱一個(gè) onNext 的觀察者,一旦得到JSON格式的數(shù)據(jù),就會(huì)經(jīng)行相應(yīng)的處理
- disposed() 是RxSwift的一個(gè)自動(dòng)內(nèi)存處理機(jī)制,類(lèi)似ARC,會(huì)自動(dòng)處理不需要的對(duì)象
/// 數(shù)據(jù) 轉(zhuǎn) 模型
extension ObservableType where E == Response {
public func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
return flatMap { response -> Observable<T> in
return Observable.just(response.mapHandyJsonModel(T.self))
}
}
}
/// 數(shù)據(jù) 轉(zhuǎn) 模型
extension Response {
func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> T {
let jsonString = String.init(data: data, encoding: .utf8)
if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {
return modelT
}
return JSONDeserializer<T>.deserializeFrom(json: "{\"msg\":\"\("common.try".L())\"}")!
}
}
extension WTApiManager {
class func NetExchangeRequest<T: BaseModel>(disposeBag: DisposeBag,type: ExchangeApiManager, model: T.Type, isBackFail: Bool = false, Success:@escaping (T)->(), Error: @escaping ()->()) {
WTExchangeProvider.rx.request(type)
.asObservable()
.mapHandyJsonModel(model)
.subscribe { (event) in
switch event {
case let .next(data):
if isBackFail {
Success(data)
break
}
guard data.status == 200 else {
WTProgressHUD.show(error: data.message ?? "common.try".L(), toView: nil)
Error()
break
}
Success(data)
break
case let .error(error):
WTDLog(error)
Error()
break
default:
break
}
}.disposed(by: disposeBag)
}
}
4 HandyJSON
class BaseModel: HandyJSON {
var status: Int = 0
var message: String? = nil // 服務(wù)端返回提示
required init(){}
}
class WTBaseModel<T: HandyJSON>: BaseModel {
var data: T? // 具體的data的格式和業(yè)務(wù)相關(guān),故用泛型定義
}
struct WTCurrencyBalanceModel: HandyJSON {
var coinCode: String = ""
let balanceAvailable: Double = 0.0
let balanceFrozen: Double = 0.0
let worth: Double = 0.0
}
// 網(wǎng)絡(luò)請(qǐng)求 傳入對(duì)應(yīng)model
WTApiManager.NetOtherRequest(disposeBag: disposeBag, type: .getMarketsPrice, model: WTBaseModel<WTRateModel>, Success: {(model) in
}) {}
