Moya(I)

安裝

Moya 介紹
? Moya 是一個基于 Alamofire 的更高層網(wǎng)絡請求封裝抽象層。Moya 也就可以看做我們的網(wǎng)絡管理層,用來封裝 URL、參數(shù)等請求所需要的一些基本信息。使用后我們的客戶端代碼會直接操作Moya,然后 Moya去管理請求,而不用跟 Alamofire 進行直接接觸。

終端查找

$ pod search Moya

最新版本和資源信息

 pod 'Moya', '~> 14.0.0-beta.2'
   - Homepage: https://github.com/Moya/Moya
   - Source:   https://github.com/Moya/Moya.git

? 我們在使用Moya時,需要引入3個第三方庫,如:Moya, Alamofire, SwiftyJSON(方便解析返回的 JSON 數(shù)據(jù))

pod 'Moya', '~> 14.0.0-beta.2'
pod 'Alamofire', '~> 5.0.0-rc.2'
pod 'SwiftyJSON', '~> 5.0.0'



使用 Moya 之前和之后

Moya 的特點

  • 編譯時檢查正確的API端點訪問。
  • 允許您定義具有關聯(lián)枚舉值的不同端點的明確用法。
  • 將測試存根視為一等公民,因此單元測試非常容易。




Moya 實戰(zhàn)



接口枚舉

//初始化請求的provider
let NetProvider = MoyaProvider<NetRequestAPI>()

/**定義請求的endpoints(供provider使用)**/
public enum NetRequestAPI {
    case channels                        //獲取頻道接口
    case playlist(String)                //獲取歌曲接口
    case otherRequest                   // 其他接口,沒有參數(shù)
}



擴展封裝

//請求配置
/*
 baseURL:服務器地址host 處理
 path:根據(jù)不同的接口,確定各個請求的具體路徑
 method:根據(jù)不同的接口,設置請求方式
 headers:統(tǒng)一配置的請求頭信息配置
 task:配置內部參數(shù),以及task信息
 */
extension NetRequestAPI: TargetType {
    
    //服務器地址
    public var baseURL: URL {
        switch self {
        case .channels:
            return URL(string: "https://www.douban.com")!
            
        case .playlist(_):
            return URL(string: "https://douban.fm")!
        case .otherRequest:
            return URL(string: "https://douban.fm/default.html")!
        }

    }
    
    // 各個請求的具體路徑
    public var path: String {
        switch self {
        case .channels:
            return "/j/app/radio/channels"
            
        case .playlist(_):
            return "/j/mine/playlist"
            
        case .otherRequest:
            return "/default/otherRequest"
        }
    }
    
    
    //請求方式
    public var method: Moya.Method {
        switch self {
        case .channels:
            return .get
            
        case .playlist(_):
            return .get
            
        default:
            return .post
        }
    }
    
    
    //請求任務事件(這里附帶上參數(shù))
    public var task: Task {
        var param: [String: Any] = [:]
        switch self {
        case .playlist(let channel):
            param["channel"] = channel
            param["type"] = "n"
            param["from"] = "mainsite"
            break
        
        case .channels: break
            
        case .otherRequest:
            return .requestPlain
        }
        
        return .requestParameters(parameters: param, encoding: URLEncoding.default)
    }
    
    //是否執(zhí)行Alamofire驗證
    public var validate: Bool {
        return false
    }
    
    //做單元測試模擬的數(shù)據(jù),只會在單元測試文件中有作用
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8, allowLossyConversion: true)!
    }
    
    //請求頭設置
    public var headers: [String : String]? {
        return nil
    }
    
}



第一種調用方式:在 Controller 中直接調用

override func viewDidLoad() {
        super.viewDidLoad()
        
        NetProvider.request(.channels) { (result) in
            if case let .success(response) = result { response.data
                //
                let data = try? response.mapJSON()  //請求response轉化為JSON格式
                let json = JSON(data!)  //轉化為JSON數(shù)據(jù)格式
                print("channels 接口數(shù)據(jù):\(json["channels"].arrayValue)")
            }
            
            //主線程刷新UI
            DispatchQueue.main.async {
                // self.tableView.reloadData()
            }
        }
    }

控制臺打?。?/p>

屬性data和statusCode打印
request 屬性打印



第二種調用方式:VM 模塊
功能 VM 模塊處理

/*
 MoyaProvider 是此次網(wǎng)絡請求的信息提供者
 MoyaProvider 根據(jù)模塊 NetRequestAPI 設置的信息綁定數(shù)據(jù)請求
 MoyaProvider 通過調用 request 方法傳出此次請求的接口,但是參數(shù)需要應用層提供!
 獲取回調信息,然后進行 json 序列化!
 最后利用函數(shù)式編程思想回調 攜帶信息的閉包 給應用層
 */

//登錄模塊管理
class LoginViewModel: NSObject {
    static let manager = LoginViewModel()
    
    //驗證碼事件
    func getChannel(username: String?, complete:@escaping((Any)-> Void)) {
        let provider = MoyaProvider<NetRequestAPI>()
        
        provider.request(.channels) { (result) in
            switch result {
            case let .success(response):
                let dict = JSON(response.data)
                complete(dict)
                
            case let .failure(error):
                print(error)
                complete("")
            }
        }
    }
}

  • .success(Moya.Response):成功的情況。我們可以從 Moya.Response 中得到返回數(shù)據(jù)(data)和狀態(tài)(status );
  • .failure(MoyaError):失敗的情況。這里的失敗指的是服務器沒有收到請求(例如可達性/連接性錯誤)或者沒有發(fā)送響應(例如請求超時)。我們可以在這里設置個延遲請求,過段時間重新發(fā)送請求。

? 對于.failure(error) 情況下,我們還可以通過 switch 語句判斷具體的 MoyaError 錯誤類型:

case let .failure(error):
    switch error {
    case .imageMapping(let response):
        print("錯誤原因:\(error.errorDescription ?? "")")
        print(response)
    case .jsonMapping(let response):
        print("錯誤原因:\(error.errorDescription ?? "")")
        print(response)
    case .statusCode(let response):
        print("錯誤原因:\(error.errorDescription ?? "")")
        print(response)
    case .stringMapping(let response):
        print("錯誤原因:\(error.errorDescription ?? "")")
        print(response)
    case .underlying(let error, let response):
        print("錯誤原因:\(error.errorDescription ?? "")")
        print(error)
        print(response as Any)
    case .requestMapping:
        print("錯誤原因:\(error.errorDescription ?? "")")
        print("nil")
    }



過濾正確狀態(tài)碼
? 除了連接超時這樣的網(wǎng)絡問題會返回.failure,像是服務器報錯(404)、請求未授權(401)等都是返回.success 的。這樣我們還需要根據(jù)狀態(tài)碼來判斷返回數(shù)據(jù)是否是正確的。
Moya.Response 本身就提供了下面兩個方法來過濾 response 響應:

  • filterSuccessfulStatusCodes():返回狀態(tài)碼為 200 - 299 的響應
  • filterSuccessfulStatusAndRedirectCodes():返回狀態(tài)碼為 200 - 399 的響應
let provider = MoyaProvider<NetRequestAPI>()

provider.request(.channels) { (result) in
    switch result {
    case let .success(response):
        do {
            //過濾成功的狀態(tài)碼響應
            try response.filterSuccessfulStatusCodes()
            let data = try response.mapJSON()
            let dict = JSON(data)
            complete(dict)
            //繼續(xù)做一些其它事情....
        }
        catch {
            //處理錯誤狀態(tài)碼的響應...
        }
        
        
    case let .failure(error):
        print(error)
        complete("")
    }
}



Controller 模擬調用

override func viewDidLoad() {
        super.viewDidLoad()
        
        //應用層只需要為此次網(wǎng)絡提供信息參數(shù)
        //在回調閉包拿到信息,處理其他業(yè)務就OK!
        //VM層調用
        LoginViewModel.manager.getChannel(username: nil) { [weak self](responseData) in
            //self?.smscodeTF.text = smscode
            print("Controller調用:\(responseData)")
        }
}

打印圖:


JSON 數(shù)據(jù)



封裝一個請求/結果處理的適配器
? 根據(jù)項目需求我們可以自行定義一個適配器(adapter),將請求和結果的判斷處理都封裝起來。然后通過三個回調函數(shù)返回相應的結果,這三個回調函數(shù)分別對應三種響應情況:

  • success:服務器連接成功,且數(shù)據(jù)正確返回(同時會自動將數(shù)據(jù)轉換成 JSON 對象,方便使用)
  • error:服務器連接成功,但數(shù)據(jù)返回錯誤(同時會返回錯誤信息)
  • failure :服務器連接不上,網(wǎng)絡異常等(同時會返回錯誤信息。必要的話,還可以在此增加自動重新請求的機制。)
struct Network {
    static let provider = MoyaProvider<DouBan>()
     
    static func request(
        _ target: DouBan,
        success successCallback: @escaping (JSON) -> Void,
        error errorCallback: @escaping (Int) -> Void,
        failure failureCallback: @escaping (MoyaError) -> Void
        ) {
        provider.request(target) { result in
            switch result {
            case let .success(response):
                do {
                    //如果數(shù)據(jù)返回成功則直接將結果轉為JSON
                    try response.filterSuccessfulStatusCodes()
                    let json = try JSON(response.mapJSON())
                    successCallback(json)
                }
                catch let error {
                    //如果數(shù)據(jù)獲取失敗,則返回錯誤狀態(tài)碼
                    errorCallback((error as! MoyaError).response!.statusCode)
                }
            case let .failure(error):
                //如果連接異常,則返滬錯誤信息(必要時還可以將嘗試重新發(fā)起請求)
                //if target.shouldRetry {
                //    retryWhenReachable(target, successCallback, errorCallback,
                //      failureCallback)
                //}
                //else {
                    failureCallback(error)
                //}
            }
        }
    }
}



Moya模型總結:CFNextwork -> Alamofire -> Moya -> 業(yè)務層

Moya 直接調用網(wǎng)絡邏輯流程圖

弊端:

  • 若整個項目是RxSwift(畢竟現(xiàn)在函數(shù)響應式編程已成為趨勢),顯然我們更需要序列,因為序列可以直接綁定響應UI,更便于開發(fā);
  • 當前直接使用 Moya ,還缺了很嚴重的一步即:模型化。




RxSwift 結合 Aoya



真實的網(wǎng)絡架構層的模塊希望是下面這樣的:
Moya模型總結:CFNextwork -> Alamofire -> Moya -> 模型化 -> RxSwift -> 業(yè)務層

RxSwift 結合 Moya



首先要使用CocoaPodsPodfile 文件中進行如下配置:

pod 'Moya', '~> 14.0.0-beta.2' 
pod 'Alamofire', '~> 5.0.0-rc.2'
pod 'SwiftyJSON', '~> 5.0.0'
pod 'ObjectMapper', '~> 3.5.1'

導入文件:

@_exported import Alamofire
@_exported import SwiftyJSON
@_exported import ObjectMapper



實戰(zhàn)代碼如下:

/*
 首先拓展 PrimitiveSequence 實際對象是處理 Moya.Response
 通過調用 SwiftyJSON 把 Response 的 data 解析成 json
 然后調用 ObjectMapper 轉成相應模型數(shù)據(jù)
 數(shù)組模型處理差不多,大家只要返回 [T] 就 OK
 */
extension PrimitiveSequence where Trait == 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)
                }
            })
        }
}

外界調用

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)





參考資料

網(wǎng)絡抽象層庫Moya的使用詳解4
Moya網(wǎng)絡層框架的學習筆記 US
Moya使用理解 US
moya的使用 US
系統(tǒng)性學習Moya+Alamofire+RxSwift+ObjectMapper的配合使用 US

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

相關閱讀更多精彩內容

  • feisky云計算、虛擬化與Linux技術筆記posts - 1014, comments - 298, trac...
    不排版閱讀 4,277評論 0 5
  • 0x0000000A 1、主要是由于安裝了有缺陷或不兼容的硬件(BIOS)、驅動程序、軟件產(chǎn)生。2、當系統(tǒng)升級WI...
    Muscleape閱讀 3,512評論 0 3
  • 本文記錄整個配置過程,供新入手MacBook和覺得MacBook比較難用的同學參考。 1、硬件提升 筆記本電腦的特...
    xhxh閱讀 6,041評論 1 53
  • CocoaPods 是什么? CocoaPods 是一個負責管理 iOS 項目中第三方開源庫的工具。CocoaPo...
    朝洋閱讀 25,978評論 3 50
  • 冰冰說,不論會不會寫,也不管寫什么,只管清理,內在會告訴自己,放下期待,它會告訴你,我想,那就試試吧,對于我這個初...
    Janewei閱讀 155評論 1 2

友情鏈接更多精彩內容