你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 通過 Moya + RxSwift + Argo 完成網絡請求

通過 Moya + RxSwift + Argo 完成網絡請求

編輯:IOS開發基礎

1.jpg

作者:@請叫我汪二 授權本站轉載。

最近在新項目中嘗試使用 Moya+RxSwift+Argo 進行網絡請求和解析,感覺還闊以,再來給大家安利一波。

Moya

Moya 是一個基於 Alamofire 的更高層網絡請求封裝,深入學習請參見官方文檔:Moya/Docs。

使用 Moya 之後網絡請求一般長了這樣:

provider.request(.UserProfile("ashfurrow")) { (data, statusCode, response, error) in
    if let data = data {
        // do something with the data
    }
}

Moya 提供了很多不錯的特性,其中我感覺最棒的是 stub ,配合 sampleData 分分鐘就完成了單元測試:

private let provider = MoyaProvider(stubClosure: MoyaProvider.ImmediatelyStub)

注意這裡的 MoyaProvider.ImmediatelyStub ,我原以為它是個枚舉類型,看了 MoyaProvider 定義發現這裡應該傳個 closure ,看了 ImmediatelyStub 的定義發現原來它是個類方法:

public typealias StubClosure = Target -> Moya.StubBehavior

override public init(stubClosure: StubClosure = MoyaProvider.NeverStub, ...) {

}

public final class func ImmediatelyStub(_: Target) -> Moya.StubBehavior {
    return .Immediate
}

如果想打印每次請求的參數,在組裝 endpoint 的時候打印即可:

private func endpointMapping(target: Target) -> Endpoint {
    if let parameters = target.parameters {
        log.verbose("\(parameters)")
    }
    return MoyaProvider.DefaultEndpointMapping(target)
}

private let provider = RxMoyaProvider(endpointClosure: endpointMapping)

RxSwift

RxSwift 前面強行安利過兩波,在此不再贅述啦,Moya 本身提供了 RxSwift 擴展,可以無縫銜接 RxSwiftReactiveCocoa ,於是打開方式變成了這樣:

private let provider = RxMoyaProvider()
private var disposeBag = DisposeBag()

extension ItemAPI {
    static func getNewItems(completion: [Item] -> Void) {
        disposeBag = DisposeBag()
        provider
            .request(.GetItems())
            .subscribe(
                onNext: { items in
                    completion(items)
                }
            )
            .addDisposableTo(disposeBag)
    }
}

Moya 的核心開發者、同時也是 Artsy 的成員:Ash Furrow, 在 AltConf 做過一次 《Functional Reactive Awesomeness With Swift》 的分享,推薦大家看一下,很可愛的!

Argo

Argothoughtbot 開源的函數式 JSON 解析轉換庫。說到 thoughtbot 就不得不提他司關於 JSON 解析質量很高的一系列文章:

  • Efficient JSON in Swift with Functional Concepts and Generics

  • Real World JSON Parsing with Swift

  • Parsing Embedded JSON and Arrays in Swift

  • Functional Swift for Dealing with Optional Values

Argo 基本上就是沿著這些文章的思路寫出來的,相關的庫還有 Runes 和 Curry。

使用 ArgoJSON 解析很有意思,大致長這樣:

struct Item {
    let id: String
    let url: String
}

extension Item: Decodable {
    static func decode(j: JSON) -> Decoded {
        return curry(Item.init)
             j <| "id"
             j <| "url"
    }
}

至於這其中各種符號的緣由,在幾篇博客中都有講解,還是挺有意思滴。

All

說完這三者,如何把它們串起來呢?Emergence 中的 Observable/Networking 給了我們答案。稍微整理後如下:

enum ORMError : ErrorType {
    case ORMNoRepresentor
    case ORMNotSuccessfulHTTP
    case ORMNoData
    case ORMCouldNotMakeObjectError
}

extension Observable {
    private func resultFromJSON(object:[String: AnyObject], classType: T.Type) -> T? {
        let decoded = classType.decode(JSON.parse(object))
        switch decoded {
        case .Success(let result):
            return result as? T
        case .Failure(let error):
            log.error("\(error)")
            return nil
            
        }
    }
    
    func mapSuccessfulHTTPToObject(type: T.Type) -> Observable {
        return map { representor in
            guard let response = representor as? MoyaResponse else {
                throw ORMError.ORMNoRepresentor
            }
            guard ((200...209) ~= response.statusCode) else {
                if let json = try? NSJSONSerialization.JSONObjectWithData(response.data, options: .AllowFragments) as? [String: AnyObject] {
                    log.error("Got error message: \(json)")
                }
                throw ORMError.ORMNotSuccessfulHTTP
            }
            do {
                guard let json = try NSJSONSerialization.JSONObjectWithData(response.data, options: .AllowFragments) as? [String: AnyObject] else {
                    throw ORMError.ORMCouldNotMakeObjectError
                }
                return self.resultFromJSON(json, classType:type)!
            } catch {
                throw ORMError.ORMCouldNotMakeObjectError
            }
        }
    }

    func mapSuccessfulHTTPToObjectArray(type: T.Type) -> Observable {
        return map { response in
            guard let response = response as? MoyaResponse else {
                throw ORMError.ORMNoRepresentor
            }
            
            // Allow successful HTTP codes
            guard ((200...209) ~= response.statusCode) else {
                if let json = try? NSJSONSerialization.JSONObjectWithData(response.data, options: .AllowFragments) as? [String: AnyObject] {
                    log.error("Got error message: \(json)")
                }
                throw ORMError.ORMNotSuccessfulHTTP
            }
            
            do {
                guard let json = try NSJSONSerialization.JSONObjectWithData(response.data, options: .AllowFragments) as? [[String : AnyObject]] else {
                    throw ORMError.ORMCouldNotMakeObjectError
                }
                
                // Objects are not guaranteed, thus cannot directly map.
                var objects = [T]()
                for dict in json {
                    if let obj = self.resultFromJSON(dict, classType:type) {
                        objects.append(obj)
                    }
                }
                return objects
                
            } catch {
                throw ORMError.ORMCouldNotMakeObjectError
            }
        }
    }
}

這樣在調用的時候就很舒服了,以前面的 Item 為例:

private let provider = RxMoyaProvider()
private var disposeBag = DisposeBag()

extension ItemAPI {
    static func getNewItems(records:[Record] = [], needCount: Int, completion: [Item] -> Void) {
        disposeBag = DisposeBag()
        provider
            .request(.AddRecords(records, needCount))
            .mapSuccessfulHTTPToObjectArray(Item)
            .subscribe(
                onNext: { items in
                    completion(items)
                }
            )
            .addDisposableTo(disposeBag)
    }
}

一個 mapSuccessfulHTTPToObjectArray 方法,直接將 JSON 字符串轉換成了 Item 對象,並且傳入了後面的數據流中,所以在 onNext 訂閱的時候傳入的就是 [Item] 數據,並且這個轉換過程還是可以復用的,且適用於所有網絡請求中 JSONModel 的轉換。爽就一個字,我只說一次。

爽!

Next

匆匆讀了一點 Emergence 和 Eidolon 的項目源碼,沒有深入不過已經受益匪淺。通過 bundle 管理 id 和 key 直接解決了我當初糾結已久的『完整項目開源如何優雅地保留 git 記錄且保護項目隱私』的問題,還有 Moya/RxSwiftMoya/ReactiveCocoa 這種子模塊化處理也在共有模塊管理這個問題上給了我一些啟發。

真是很喜歡 Artsy 這樣的團隊,大家都一起做著自己喜歡的事情,還能站著把錢賺了。

所幸的是我也可以這樣做自己喜歡的事情了,不過不賺錢。具體狀況後面單獨開一篇閒扯扯。

碎告。


參考資料:

  • RxSwift

  • Moya

  • Argo

  • Emergence

  • Eidolon

  • Efficient JSON in Swift with Functional Concepts and Generics

  • Real World JSON Parsing with Swift

  • Parsing Embedded JSON and Arrays in Swift

  • Functional Swift for Dealing with Optional Values

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved