Swift - 當Moya趕上RxSwift(網絡架構優化)

1、Moya 面向協議網絡編程

1️⃣:Moya初探

若是你上面的POP面向協議編程已經看得差很少了,那麼這個模塊內容是很是簡單的!git

常規網絡層在iOS應用程序中很常見。它們很差有幾個緣由:github

  • 讓編寫新的應用程序變得困難(「我從哪裏開始?」)
  • 很難維護現有的應用程序(「哦,天哪,這一團糟……」)
  • 使編寫單元測試變得困難(「我如何再作一次?」)

Moya 的基本思想是:咱們須要一些網絡抽象層,可以充分封裝直接調用Alamofire。它應該足夠簡單,普通的事情很容易,可是足夠全面,複雜的事情也很容易。編程

  • Moya 的特色有如下幾點:
  • 編譯時檢查正確的API端點訪問。
  • 容許您定義具備關聯枚舉值的不一樣端點的明確用法。
  • 將測試存根視爲一等公民,所以單元測試很是容易。

2️⃣:Moya開發使用

1:接口枚舉json

public enum LGLoginAPI {
    case login(String, String, String)  // 登陸接口
    case smscode(String)                // 登陸,發送驗證碼
    case otherRequest                   // 其餘接口,沒有參數
}
複製代碼
  • 這個接口枚舉提供這個登陸註冊模塊的全部接口
extension LGLoginAPI: TargetType {
    //服務器地址
    public var baseURL: URL {
        return URL(string:"http://127.0.0.1:5000/")!
    }
    // 各個請求的具體路徑
    public var path: String {
        switch self {
        case .login:
            return "login/"
        case .smscode:
            return "login/smscode/"
        case .otherRequest:
            return "login/otherRequest/"
        }
    }
    // 請求方式
    public var method: Moya.Method {
        switch self {
        case .login:
            return .post
        case .smscode:
            return .post
        default:
            return .get
        }
    }
    //這個就是作單元測試模擬的數據,只會在單元測試文件中有做用
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
    //請求任務事件(這裏附帶上參數)
    public var task: Task {
        var param:[String:Any] = [:]
        switch self {
        case .login(let username,let password,let smscode):
            param["username"] = username
            param["password"] = password
            param["smscode"] = smscode
        case .smscode(let username):
            param["username"] = username
        default:
            return .requestPlain
        }
        return .requestParameters(parameters: param, encoding: URLEncoding.default)
    }
    //設置請求頭
    public var headers: [String: String]? {
        return nil
    }
}
複製代碼
  • baseURL:服務器地址host 處理
  • path:根據不一樣的接口,肯定各個請求的具體路徑
  • method:根據不一樣的接口,設置請求方式
  • headers:統一配置的請求頭信息配置
  • task:配置內部參數,以及task信息

2:登陸模塊網絡管理者api

class LGLoginClient: NSObject {
    static let manager = LGLoginClient()
    
    //MARK: - 驗證碼事件
    func smscode(username:String,complete:@escaping ((String) -> Void)) {
        let provide = MoyaProvider<LGLoginAPI>()
        provide.request(.smscode(username)) { (result) in
            switch result{
            case let .success(response):
                let dict = LGLoginClient.lgJson(data: response.data)
                complete(dict["smscode"] as! String)
            case let .failure(error):
                print(error)
                complete("")
            }
        }
    }
}
複製代碼
  • MoyaProvider 是這次網絡請求的信息提供者
  • MoyaProvider 根據模塊 LGLoginAPI 設置的信息綁定數據請求
  • MoyaProvider 經過調用 request 方法傳出這次請求的接口,可是參數須要應用層提供!
  • 獲取回調信息,而後進行 json 序列化!
  • 最後利用函數式編程思想回調 攜帶信息的閉包 給應用層

3:應用層調用數組

@IBAction func didClickCodeBtn(_ sender: Any) {
    LGLoginClient.manager.smscode(username: username) { [weak self](smscode) in
        self?.smscodeTF.text = smscode
    }
}
複製代碼
  • 應用層只須要爲這次網絡提供信息參數
  • 在回調閉包拿到信息,處理其餘業務就OK!

Moya模型總結:CFNextwork -> Alamofire -> Moya -> 業務層服務器

3️⃣:Moya直接使用的弊端

  • 若是整個項目是 RxSwift(畢竟如今函數響應式編程已成爲趨勢),顯然咱們更須要序列,由於序列能夠直接綁定響應UI,更便於開發!
  • 當前只是這麼直接使用 Moya 對咱們來講還缺了很嚴重的一步:模型化
  • 說白了咱們的網絡整個架構層的模塊但願是下面這樣的:

Moya模型總結:CFNextwork -> Alamofire -> Moya -> 模型化 -> RxSwift -> 業務層網絡

2、RxMoya 序列化

爲了可以增長 Moya 序列化的能力,作了 Reactive 拓展!閉包

public extension Reactive where Base: MoyaProviderType {
/// Designated request-making method with progress.
    public func requestWithProgress(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable<ProgressResponse> {
        let progressBlock: (AnyObserver) -> (ProgressResponse) -> Void = { observer in
            return { progress in
                observer.onNext(progress)
            }
        }
        let response: Observable<ProgressResponse> = Observable.create { [weak base] observer in
            let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: progressBlock(observer)) { result in
                switch result {
                case .success:
                    observer.onCompleted()
                case let .failure(error):
                    observer.onError(error)
                }
            }
            return Disposables.create {
                cancellableToken?.cancel()
            }
        }
}
複製代碼
  • 1:觀察者發送響應網絡進度的序列 observer.onNext(progress)
  • 2:提供成功或者失敗的序列 observer.onCompleted()observer.onError(error)
  • 3:對此次 RxSwift 封裝的銷燬對外提供,外界隨時隨地斷開響應關係!
  • 4:說白了也就是對 Moya 包裝一層 RxSwift
  • 5:外界調用:MoyaProvider 初始化對象調用:rx.request(target)

3、RxMoya 模型化

畢竟咱們開發人員更多的關注是模型,而非 data、抑或 json架構

extension PrimitiveSequence where TraitType == SingleTrait, Element == Moya.Response {

    func map<T: ImmutableMappable>(_ type: T.Type) -> PrimitiveSequence<TraitType, T> {
       return self
           .map { (response) -> T in
             let json = try JSON(data: response.data)
             guard let code = json[RESULT_CODE].int else { throw RequestError.noCodeKey }
             if code != StatusCode.success.rawValue { throw RequestError.sysError(statusCode:"\(code)" , errorMsg: json[RESULT_MESSAGE].string) }
            if let data = json[RESULT_DATA].dictionaryObject {
                return try Mapper<T>().map(JSON: data)
            }else if let data = json[RESULT_RESULT].dictionaryObject {
                return try Mapper<T>().map(JSON: data)
            }
             throw RequestError.noDataKey
        }.do(onSuccess: { (_) in
            
        }, onError: { (error) in
            if error is MapError {
                log.error(error)
            }
        })
    }
}
複製代碼
  • 首先拓展 PrimitiveSequence 實際對象是處理 Moya.Response
  • 經過調用 SwiftyJSONResponsedata 解析成 json
  • 而後調用 ObjectMapper 轉成相應模型數據
  • 數組模型處理差很少,你們只要返回 [T] 就 OK

4、外界調用

loginService.login().asObservable()
    .subscribe(onNext: {[weak self] (rcmdBranchModel) in
        
        guard let `self` = self else { return }
        self.requestIds = rcmdBranchModel.tab.map{$0.id}
        self.menuTitles += rcmdBranchModel.tab.map{$0.name}
        self.pageController.magicView.reloadData(toPage: 1)
    })
    .disposed(by: disposeBag)
複製代碼
  • 清爽乾淨,耦合度大大下降,複用性大大提升!
  • 業務層只對業務需求負責

5、總結

1️⃣:響應下沉

  • 從業務層流出請求響應的必要條件
  • 模塊層 LGLoginClient 接受業務層的響應及時處理條件交付給 MoyaProvider
  • MoyaProvider 也就是咱們的 Moya 層調度集中管理繼續下沉給 Alamofire
  • Alamofire 包裝處理請求,交給 CFNextwork 去真正處理網絡下層

2️⃣:回調上浮

  • CFNextwork 請求得到 Response 經過代理交付給 Alamofire
  • Alamofire 經過包裝、驗證、序列化回調給封裝它的 Moya
  • Moya 這一層經過 SwiftyJSON 進行 json化,通過 ObjectMapper 進行 模型化
  • Moya 再通過 RxSwift 進行 序列化
  • 經過 模塊網絡層 回調出去相應的 模型序列 給應用層

3️⃣:小結: 一圖勝言

整個流程構成一個閉環,層層下沉,層層上浮!架構思路很是清晰!

當北京趕上西雅圖(人生大幸),當 Moya 趕上 RxSwift (開發大幸)!

架構設計思惟並非一天就能養成 (Rome was not built in a day) 可是若是你是一箇中高級開發,那麼架構設計思惟你必須擁有。前進道路一點一滴,慢慢積累。上帝也會可憐你.....💪💪💪

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!

相關文章
相關標籤/搜索