項(xiàng)目剖析03-swift 網(wǎng)絡(luò)請(qǐng)求Moya+HandyJSON+RxSwift

項(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ò)框架是搭著HandyJSONRxSwift一起構(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è)插件

  1. AccessTokenPlugin // 管理AccessToken的插件
  2. CredentialsPlugin // 管理認(rèn)證的插件
  3. NetworkActivityPlugin // 管理網(wǎng)絡(luò)狀態(tài)的插件
  4. NetworkLoggerPlugin // 管理網(wǎng)絡(luò)log的插件

3 RxSwift

這里的RxSwift不是完整的RxSwift,而是為Moya定制的一個(gè)擴(kuò)展(pod 'Moya/RxSwift')在數(shù)據(jù)請(qǐng)求回來(lái)后進(jìn)行處理。

  1. request() 傳入API
  2. asObservable() 是Moya為RxSwift提供的擴(kuò)展方法,返回可監(jiān)聽(tīng)序列
  3. mapHandyJsonModel() 也是Moya RxSwift的擴(kuò)展方法進(jìn)行自定義的,可以把返回的數(shù)據(jù)解析成model
  4. subscribe() 是對(duì)處理過(guò)的 Observable 訂閱一個(gè) onNext 的觀察者,一旦得到JSON格式的數(shù)據(jù),就會(huì)經(jīng)行相應(yīng)的處理
  5. 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
}) {}

5 以上就是我在項(xiàng)目中使用Moya+HandyJSON+RxSwift的方法,如果有錯(cuò)誤或者不足之處還望指正,謝謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容