Moya+PromiseKit+RxSwift優雅的書寫網絡請求


https://www.jianshu.com/p/0d0f818be6c4

 

2017.04.13 15:07* 字數 1574 閱讀 3632評論 16喜歡 27

前言

公司之前的項目是由其他同事搭建的,隨着公司業務的拓展,網絡請求隨之增加。網絡工具類內部的代碼愈發龐大,最終難以管理。為此尋找一個可行的解決方案,順便學習一下RxSwift的使用。不說那么多底層原理,直接咱就說怎么用、怎么寫,通俗易懂。文章附demo源碼,由於本人也在學習中,所以代碼中難免存在疏漏,存在問題可以互相討論。demo內api選自公司內部api,所以不過分使用,僅作實例而已。

博客園demo

https://github.com/sundaysmeSwift/Moyastudy

環境配置

  • Xcode 8.3
  • Swift 3

cocoapods

  • Alamofire
  • ObjectMapper
  • Moya
  • RxSwift
  • RxCocoa
  • PromiseKit

其中,數據解析部分(ObjectMapper)我們根據各自公司的習慣,可以選擇Argo,SwiftyJSON等等其他框架,這不影響本博客內容。

實例

Moya部分

Moya是一個網絡抽象層庫。它在Alamofire基礎上提供了一系列簡單的抽象接口,讓客戶端代碼不用去直接調用Alamofire,同時提供了很多實用的功能。

創建請求

Moya的TargetType協議規定使用枚舉來創建網絡請求,我們可以通過枚舉來區分不同業務的網絡請求。例如:

enum UserAPI {

    case registerUser(name: String, password: String)

    case upload(avator: UIImage)

}

 

enum homepageAPI {

    case homepageData

    case homepageBannerData

}

 

這里我們使用demo里ApiExample的實例,分別表示一個get、post和一個上傳圖片的請求,下載的請求一般只有特殊的業務里才會用到,這里不做展示。

enum ApiExample {

    case frontpage

    case fetchMorePackageArts(count: Int, artStyleId: Int, artType: Int)

    case updateExample(image: UIImage, otherParameter: String)

}

定義請求枚舉完成后,我們需要在枚舉的拓展里實現Moya的TargetType協議。

extension ApiExample: TargetType {

    

    public var baseURL: URL {

        return URL(string: "http://zuzu.artally.com.cn/zuzuart/")!

    }

    

    public var path: String {

        switch self {

        case .frontpage:

            return "frontpage/index/list/"

        case .fetchMorePackageArts:

            return "work/banner/work/list10/"

        case .updateExample:

            return "這里不提供實例,使用偽代碼"

        }

    }

    

    public var method: Moya.Method {

        switch self {

        case .frontpage:

            return .get

        case .fetchMorePackageArts, .updateExample:

            return .post

        }

    }

    

    public var parameters: [String: Any]? {

        switch self {

        case .frontpage:

            return nil

        case .fetchMorePackageArts(let count, let artStyleId, let artType):

            return ["count": count, "banner_style_id": artStyleId, "type": artType]

        case .updateExample(_, let otherParameter):

            // 這里返回除圖片之外的所有參數

            return ["參數名":otherParameter]

        }

    }

    

    // Local data for unit test.use empty data temporarily.

    public var sampleData: Data {

        return "".data(using: .utf8)!

    }

    

    // Represents an HTTP task.

    public var task: Task {

        switch self {

        case .updateExample(let image, _):

            let data = UIImageJPEGRepresentation(image, 0.7)

            let img = MultipartFormData(provider: .data(data!), name: "參數名", fileName: "名稱隨便寫.jpg", mimeType: "image/jpeg")

            return .upload(.multipart([img]))

        default:

            return .request

        }

    }

   

    public var parameterEncoding: ParameterEncoding {

        // Select type of parameter encoding based on requirements.Usually we use 'URLEncoding.default'.

        /*

        if self.method == .get || self.method == .head {

            return URLEncoding.default

        } else {

            return JSONEncoding.default

        }

        */

        return URLEncoding.default

    }

 

這里對幾個特殊的參數做出解釋。sampleData是單元測試等等需要的假數據,只在有測試的需求下才用得到,一般我們返回一個空的數據就可以。parameterEncoding是參數編碼方式,通常我們使用URLEncoding就可以,根據不同的需求去選擇,如實例中注釋代碼。

插件機制

如果我們想在請求前和請求后做一些操作,Moya提供自定義請求插件來實現這一需求。比如我們要做請求完成前顯示菊花,請求完成或失敗后隱藏菊花,我們可以這樣做。自定義插件需要遵守PluginType協議,根據不同需求實現協議里的方法。實例:

public final class RequestLoadingPlugin: PluginType {

    

    public func willSend(_ request: RequestType, target: TargetType) {

        // show loading

    }

    

    public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {

        // hide loading

    }

    

}

ObjectMapper解析數據

我們使用ObjectMapper解析數據映射為model,ObjectMapper的使用這里不作介紹,請看官方文檔。

我們為RxSwift中Observable寫一個拓展,用於解析數據。通常我們服務器后台返回的數據格式類似以下形式,有的直接是一個我們要用到的json數據,有的在數據外還包一層表示請求狀態的無用數據(code,msg 等等):

// success

{

code: 200,

msg: "success",

work_info: { }

}

 

or

 

{

work_comments: [ ]

}

 

// error

{

code: 500,

error: "token error",

}

所以在拓展里面提供兩個方法去解析模型和數組,並可選的傳入key值來解析返回的json數據中字典對應的key值。

func mapObject<T: Mappable>(type: T.Type, key: String? = nil) -> Observable<T> {

}

    

func mapArray<T: Mappable>(type: T.Type, key: String? = nil) -> Observable<[T]> {

}

 

另外我們可以自定義錯誤類型來處理數據解析中發生的錯誤。

enum RxSwiftMoyaError: String {

    case ParseJSONError

    case OtherError

    // you can define other error 

}

 

 

extension RxSwiftMoyaError: Swift.Error {}

有時候請求是成功的,但是請求內容是錯誤的,錯誤信息由我們的服務器返回,如前文提及。所以我們在這個拓展里提供了一個方法解析服務器返回的錯誤信息,若有錯誤則拋出,無則返回nil

fileprivate func parseError(response: [String: Any]?) -> NSError? {

        var error: NSError?

        if let value = response {

            if let code = value["code"] as? Int, code != 200 {

                var msg = ""

                if let message = value["error"] as? String {

                    msg = message

                }

                error = NSError(domain: "Network", code: code, userInfo: [NSLocalizedDescriptionKey: msg])

            }

        }

        return error

    }

 

異步編程 PromiseKit

現代編程語言都很好的支持了異步編程,因此在swift編程中,擁有功能強大且輕量級的異步編程工具的需求變得很強烈。

這是PromiseKit中提到的,所以我們不甘落后,也想來一發。

如官方文檔中提到的異步鏈式調用,這很常見。詳細使用請看官方文檔,這里不做介紹。

login().then { json in

    //

}.catch { error in

    // handle error

}

 

例如,在本demo中我們使用MVVM模式。在viewModel中我們處理網絡請求。在demo中ViewModelExample文件內,我們書寫以下代碼。首先,我們使用Moya的RxSwift拓展,來創建一個RxMoyaProvider,用於請求數據。在這里,我們創建Provider時可以自主選擇插件,用於不同的需求。例如有些網絡請求我們並不希望用戶看到,所以不需要出現加載提示(如菊花,等等),我們在創建時就不需要加入插件。反之,我們可以在創建Provider時加入所需的插件以適應不同的需求。因此Moya變得更加靈活。實例:

let provider = RxMoyaProvider<ApiExample>(plugins: [RequestLoadingPlugin(),NetworkLogger()])

接着我們來創建一個Promise(異步任務)。

func getHomepagePageData() -> Promise<HomepageData> {

        return Promise(resolvers: { (result, error) in

            provider.request(.frontpage)

                .filterSuccessfulStatusCodes()

                .mapJSON()

                .mapObject(type: HomepageData.self)

                .subscribe(onNext: {

                    result($0)

                }, onError: {

                    error($0)

                })

                .addDisposableTo(disposeBag)

        })

    }

 

在這個方法內部,我們返回一個Promise,在實例化promise內部使用Moya請求數據。然后通過Observable拓展中的* .mapObject或者.mapArray方法來解析數據並返回,其中產生的錯誤通過.onError*回調進行處理。通過RxSwift鏈式調用,我們可以很簡潔的書寫網絡請求的代碼,是不是開始有些感覺了?

provider.request(.frontpage)

                .filterSuccessfulStatusCodes()

                .mapJSON()

                .mapObject(type: HomepageData.self)

                .subscribe(onNext: {

                    result($0)

                }, onError: {

                    error($0)

                })

                .addDisposableTo(disposeBag)

使用實例

我們在控制器里添加一個viewModel的實例,然后在網絡請求的方法是實現viewModel中定義的相關請求方法即可。實例:

lazy var viewModel = ViewModelExample()

 

func getRequestExample() {

        viewModel.getHomepagePageData().then { data in

            // fetch data to refresh UI

            print(data.msg ?? "")

        }.always {

            // optional

            // always do something before request complete,such as cache data,etc.

            print("request complete")

        }.catch { (error) in

            // handle error

            print(error)

        }

    }

 

 

通過PromiseKit直接鏈式調用,我們很簡潔的通過.then一步步的處理數據,通過.catch處理錯誤。通過Moya和RxSwift,我們發送請求和解析數據也變得靈活機動干凈利落,不拖泥帶水。同時因為我們通過枚舉來管理不同業務的請求接口,代碼邏輯也變得清晰。

總之,Moya+PromiseKit+Swift 所謂優雅的書寫網絡請求,非常值得嘗試一下!

相關參考:

學習 Swift Moya(二)- Moya + SwiftyJSON + RxSwift

如何寫出最簡潔優雅的網絡封裝 Moya + RxSwift


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM