Moya + RxSwift + ObjectMapper / HandyJson 創(chuàng)造簡(jiǎn)潔的網(wǎng)絡(luò)請(qǐng)求代碼

2017.07.05更新:

  • 移除Alamofire 不支持iOS8解決方案,Alamofire 在我當(dāng)時(shí)項(xiàng)目立項(xiàng)的時(shí)候是不支持iOS8的,所以引入了不支持iOS8解決方案,但是之后不久就不固執(zhí)的要求 iOS9+ 了,所以這篇博客里的解決方案部分就沒(méi)有什么意義了,徒增篇幅,也就移除了。
  • 增加了Demo ,點(diǎn)擊傳送至Demo
  • 增加了HanyJSON

Alamofire

在Swift中我們發(fā)送網(wǎng)絡(luò)請(qǐng)求一般都是使用一個(gè)第三方庫(kù) Alamofire ,設(shè)置好URL和parameter然后發(fā)送網(wǎng)絡(luò)請(qǐng)求,就像下面這樣:

// Alamofire 4
let parameters: Parameters = ["foo": "bar"]

Alamofire.request(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default)
    .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
        print("Progress: \(progress.fractionCompleted)")
    }
    .validate { request, response, data in
        // Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
        return .success
    }
    .responseJSON { response in
        debugPrint(response)
    }

當(dāng)然這是Alamofire官方給出的最簡(jiǎn)單使用例子,你要真的在項(xiàng)目里每個(gè)網(wǎng)絡(luò)請(qǐng)求都這么寫。。那真的是太耿直了,來(lái)一次網(wǎng)絡(luò)庫(kù)版本更新,你就知道什么是痛苦了,項(xiàng)目里遍地都是要改的代碼。

給Alamofire加個(gè)Router

我們?cè)陧?xiàng)目開(kāi)發(fā)中,很少像上邊那樣直接拿來(lái)用,這篇不是Alamofire的教程,感興趣的可以到 這里 (這里是國(guó)人翻譯版)看看是如何從零開(kāi)始一步步把Alamofire封裝成一個(gè)更加好用的框架的。

如果只想用 Alamofire不想引入 Moya我的demo里的Router.swift文件 也提供好了類似的封裝,可以直接復(fù)制,稍微修改一下就可以拿來(lái)用。點(diǎn)擊跳轉(zhuǎn)

使用封裝好的Alamofire只需要這樣:

// get請(qǐng)求  根據(jù)不同需求,可以在router里 自定義網(wǎng)絡(luò)請(qǐng)求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info,然后處理請(qǐng)求到的response
Alamofire.request(Router.getInfo(uid: "1", token: "abc")).responseJSON { response in
    // 在這里處理 請(qǐng)求到的數(shù)據(jù)
    debugPrint(response)
}

// post請(qǐng)求  根據(jù)不同需求,可以在router里 自定義網(wǎng)絡(luò)請(qǐng)求需要的字段, 業(yè)務(wù)代碼只需要關(guān)心傳入uid 和 info,然后處理請(qǐng)求到的response
Alamofire.request(Router.postInfo(uid: "1", info: "110")).responseJSON { response in
    // 在這里處理 請(qǐng)求到的數(shù)據(jù)
    debugPrint(response)
}

比上邊的直接用看起來(lái)簡(jiǎn)潔多了。

Moya

其實(shí)Moya已經(jīng)把我們上邊做的都寫好了,使用moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求是這樣的:

provider.request(.zen) { result in
    ...
    ...
}

也很簡(jiǎn)潔。同樣在我的demo里的Moya.swift文件 也提供好了Moya在項(xiàng)目里的寫法,可以直接復(fù)制,稍微修改一下就可以拿來(lái)用。點(diǎn)擊跳轉(zhuǎn),在項(xiàng)目里使用見(jiàn)ViewController.swift,是這樣的:

let provider = MoyaProvider<MyAPI>()
provider.request(.login(username: "username", password: "password")) { result in
    debugPrint(result)
}

Moya + RxSwift

接下來(lái)我們看看 Moya配合上RxSwift能有什么不同的魔法。因?yàn)槲覀円玫氖?code>Moya提供的Rx擴(kuò)展,所以Podfile里要這樣寫:

pod 'Moya/RxSwift'

發(fā)起網(wǎng)絡(luò)請(qǐng)求使用起來(lái)和單獨(dú)使用Moya差不多:

let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider(.login(username: "username", password: "password")) { result in
    debugPrint(result)
}

但是,配合起來(lái)Rx,Moya提供了更多的方便用法。

  • 比如我們剛才寫的代碼,其實(shí)對(duì)請(qǐng)求到的錯(cuò)誤信息都沒(méi)有處理。我們現(xiàn)在可以這樣寫:
rxProvider.request(.login(username: "username", password: "password"))
    .filterSuccessfulStatusCodes()
    .subscribe(onNext: {
        debugPrint($0)
    })
    .addDisposableTo(disposeBag)
  • 想要過(guò)濾出特定的code?比如說(shuō)在用戶密碼更改時(shí)和后臺(tái)約定的code:
rxProvider.request(.login(username: "username", password: "password"))
    .filter(statusCode: 0)
    .subscribe(onNext: {
        debugPrint($0)
        //在這里添加處理返回特定code的邏輯
    })
    .addDisposableTo(disposeBag)
  • 還有提供過(guò)濾 一個(gè)范圍的code:
.filter(statusCodes: 200...300)
  • 一句話把請(qǐng)求到的數(shù)據(jù)轉(zhuǎn)成JSON
rxProvider.request(.login(username: "username", password: "password"))
    .mapJSON()
    .subscribe(onNext: { json in
        debugPrint(json) //已經(jīng)幫你轉(zhuǎn)成了 JSON,在這里只需要拿到JSON進(jìn)行接下來(lái)的邏輯就可以了
    })
    .addDisposableTo(disposeBag)
  • 還可以mapString
.mapString(atKeyPath: "")

Moya + RxSwift + ObjectMapper

在上一部分,我們已經(jīng)可以將網(wǎng)絡(luò)請(qǐng)求回來(lái)的數(shù)據(jù)一鍵轉(zhuǎn)為JSON了,項(xiàng)目中肯定是經(jīng)常會(huì)遇到將JSON轉(zhuǎn)為對(duì)象的操作,ObjectMapper 就是Swift中常用的JSON轉(zhuǎn)對(duì)象框架。

將JSON轉(zhuǎn)為對(duì)象:

let object = Mapper<T>().map(JSON: data)!

具體詳細(xì)用法可以看看項(xiàng)目說(shuō)明,這里默認(rèn)已經(jīng)是有過(guò)Swift項(xiàng)目經(jīng)驗(yàn),使用過(guò)ObjectMapper,就不講解ObjectMapper的用法了,因?yàn)槿绻麑懗鰜?lái),這篇文章就太長(zhǎng)了。如果你用的是HandyJson,請(qǐng)看下一節(jié)的介紹。

接下來(lái),我們把json解析為對(duì)象的過(guò)程也簡(jiǎn)化一下。

我們請(qǐng)求回來(lái)的數(shù)據(jù)可能是這樣的:

{
    "code" : 200
   "message" : "success"
    "data" : {
        "user" : {
        "nickname" : "name"
        "age" : 18
        }
    }
}

也可能是這樣的:

{   
    "code" : 200
    "message" : "success"
    "data" : [

        "user" : {
            "nickname" : "name1"
            "age" : 18
        },

        "user" : {
            "nickname" : "name2"
            "age" : 19
        },

        "user" : {
            "nickname" : "name3"
            "age" : 13
        }
    ]

}

這就要求我們能把JSON解析為一個(gè)單獨(dú)的User對(duì)象,也要能解析成一個(gè)User對(duì)象數(shù)組

ObjectMapper + RxSwift

Podfile中添加:

pod 'ObjectMapper', '~> 2.2'

然后記得pod install

接下來(lái),創(chuàng)建RxMoyaMapper.swift文件
因?yàn)榫W(wǎng)絡(luò)請(qǐng)求和解析的過(guò)程中,會(huì)有各種錯(cuò)誤出現(xiàn),首先要在文件中定義錯(cuò)誤類型

enum DCUError : Swift.Error {
    // 解析失敗
    case ParseJSONError
    // 網(wǎng)絡(luò)請(qǐng)求發(fā)生錯(cuò)誤
    case RequestFailed
    // 接收到的返回沒(méi)有data
    case NoResponse
    //服務(wù)器返回了一個(gè)錯(cuò)誤代碼
    case UnexpectedResult(resultCode: String?, resultMsg: String?)
}

然后定義一個(gè)RequestStatus的枚舉,用來(lái)表示服務(wù)器返回給我們的code是不是約定的成功code,比方約定返回200為認(rèn)證成功,401表示用戶登錄信息失效,等等,那么我們是需要在返回認(rèn)證不成功code時(shí),進(jìn)行一些邏輯處理的(比方彈出登錄框,讓用戶重新登錄)。

enum RequestStatus: Int {
    case requestSuccess = 200
    case requestError
}

定義 code ,message ,data字段,方便后邊解析時(shí)候使用(請(qǐng)根據(jù)后臺(tái)返回內(nèi)容不同做相應(yīng)修改,這里是拿我們后臺(tái)的返回規(guī)則舉例):

let RESULT_CODE = "code"
let RESULT_MSG = "message"
let RESULT_DATA = "data"

然后我們給RxSwift 的Observable添加extension,兩個(gè)方法分別對(duì)應(yīng)解析為一個(gè)單獨(dú)的User對(duì)象,和解析成一個(gè)User對(duì)象數(shù)組:

extension Observable {
    func mapResponseToObject<T: BaseMappable>(type: T.Type) -> Observable<T> {
        return map { response in
            
            // get Moya.Response
            guard let response = response as? Moya.Response else {
                throw DCUError.NoResponse
            }
            
            // check http status
            guard ((200...209) ~= response.statusCode) else {
                throw DCUError.RequestFailed
            }
            
            guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any]  else {
                throw DCUError.NoResponse
            }
            
            
            if let code = json[RESULT_CODE] as? Int {
                if code == RequestStatus.requestSuccess.rawValue {
                    let data =  json[RESULT_DATA]
                    if let data = data as? [String: Any] {
                        let object = Mapper<T>().map(JSON: data)!
                        return object
                    }else {
                        throw DCUError.ParseJSONError
                    }
                } else {
                    throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)
                }
            } else {
                throw DCUError.ParseJSONError
            }
            
        }
    }
    
    func mapResponseToObjectArray<T: BaseMappable>(type: T.Type) -> Observable<[T]> {
        return map { response in
            
            // get Moya.Response
            guard let response = response as? Moya.Response else {
                throw DCUError.NoResponse
            }
            
            // check http status
            guard ((200...209) ~= response.statusCode) else {
                throw DCUError.RequestFailed
            }

            guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any]  else {
                throw DCUError.NoResponse
            }
            
            
            if let code = json[RESULT_CODE] as? Int {
                if code == RequestStatus.requestSuccess.rawValue {
                    var objects = [T]()
                    guard let objectsArrays = json[RESULT_DATA] as? [Any] else {
                        throw DCUError.ParseJSONError
                    }
                    for object in objectsArrays {
                        if let data = object as? [String: Any] {
                            let object = Mapper<T>().map(JSON: data)!
                            objects.append(object)
                        }
                    }
                    return objects

                } else {
                    throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)

                }
            } else {
                throw DCUError.ParseJSONError
            }
        }
    }
}

同樣在我的demo里的RxMoyaMapper.swift文件, 也提供好了完整的實(shí)現(xiàn),可以現(xiàn)在項(xiàng)目看一下,稍微修改一下就可以拿來(lái)在項(xiàng)目里使用。點(diǎn)擊跳轉(zhuǎn)

在項(xiàng)目里寫起來(lái)是這樣的:

//解析成User對(duì)象
let rxProvider = RxMoyaProvider<MyAPI>()
rxProvider.request(.login(username: "username", password: "password"))
    .mapResponseToObject(type: User.self)
    .subscribe(onNext: { user in
        debugPrint(user)
    })
    .addDisposableTo(disposeBag)

//解析成User對(duì)象數(shù)組
rxProvider.request(.login(username: "username", password: "password"))
    .mapResponseToObjectArray(type: User.self)
    .subscribe(onNext: { users in
        debugPrint(users)
    })
    .addDisposableTo(disposeBag)

Moya + RxSwift + ObjectMapper

首先在Podfile中添加:

pod 'HandyJSON', '~> 1.7.2'

然后

pod install

我們可以直接來(lái)之前定義的DCUError來(lái)用,其他的實(shí)現(xiàn)也都很類似,只需要把解析的部分換成handyJSON就行了:

let object = JSONDeserializer<T>.deserializeFrom(json: jsonString)
let objArray = JSONDeserializer<T>.deserializeModelArrayFrom(array: objectsArrays)

使用起來(lái)也是一模一樣的:

func handyJSON() {
    //解析成People對(duì)象
    let rxProvider = RxMoyaProvider<MyAPI>()
    rxProvider.request(.login(username: "username", password: "password"))
        .mapResponseToObject(type: People.self)
        .subscribe(onNext: { _ in
            
        })
        .addDisposableTo(disposeBag)
    
    //解析成People對(duì)象數(shù)組
    rxProvider.request(.login(username: "username", password: "password"))
        .mapResponseToObjectArray(type: People.self)
        .subscribe(onNext: { users in
            debugPrint(users)
        })
        .addDisposableTo(disposeBag)
}

同樣,handyJSON的代碼我也放在我的demo里的RxHandyJSON.swift文件了, 提供好了完整的實(shí)現(xiàn),可以現(xiàn)在項(xiàng)目看一下,稍微修改一下就可以拿來(lái)在項(xiàng)目里使用。點(diǎn)擊跳轉(zhuǎn)

最后編輯于
?著作權(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)容