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

2017.07.05更新:

  • 移除Alamofire 不支持iOS8解決方案轨帜,Alamofire 在我當(dāng)時項目立項的時候是不支持iOS8的哎迄,所以引入了不支持iOS8解決方案,但是之后不久就不固執(zhí)的要求 iOS9+ 了唱矛,所以這篇博客里的解決方案部分就沒有什么意義了缺前,徒增篇幅,也就移除了监氢。
  • 增加了Demo 布蔗,點擊傳送至Demo
  • 增加了HanyJSON

Alamofire

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

// 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官方給出的最簡單使用例子纵揍,你要真的在項目里每個網(wǎng)絡(luò)請求都這么寫。议街。那真的是太耿直了泽谨,來一次網(wǎng)絡(luò)庫版本更新,你就知道什么是痛苦了特漩,項目里遍地都是要改的代碼吧雹。

給Alamofire加個Router

我們在項目開發(fā)中,很少像上邊那樣直接拿來用涂身,這篇不是Alamofire的教程雄卷,感興趣的可以到 這里 (這里是國人翻譯版)看看是如何從零開始一步步把Alamofire封裝成一個更加好用的框架的。

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

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

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

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

比上邊的直接用看起來簡潔多了拂盯。

Moya

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

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

也很簡潔记靡。同樣在我的demo里的Moya.swift文件 也提供好了Moya在項目里的寫法谈竿,可以直接復(fù)制团驱,稍微修改一下就可以拿來用。點擊跳轉(zhuǎn)空凸,在項目里使用見ViewController.swift嚎花,是這樣的:

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

Moya + RxSwift

接下來我們看看 Moya配合上RxSwift能有什么不同的魔法。因為我們要用的是Moya提供的Rx擴展呀洲,所以Podfile里要這樣寫:

pod 'Moya/RxSwift'

發(fā)起網(wǎng)絡(luò)請求使用起來和單獨使用Moya差不多:

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

但是紊选,配合起來Rx,Moya提供了更多的方便用法道逗。

  • 比如我們剛才寫的代碼兵罢,其實對請求到的錯誤信息都沒有處理。我們現(xiàn)在可以這樣寫:
rxProvider.request(.login(username: "username", password: "password"))
    .filterSuccessfulStatusCodes()
    .subscribe(onNext: {
        debugPrint($0)
    })
    .addDisposableTo(disposeBag)
  • 想要過濾出特定的code滓窍?比如說在用戶密碼更改時和后臺約定的code:
rxProvider.request(.login(username: "username", password: "password"))
    .filter(statusCode: 0)
    .subscribe(onNext: {
        debugPrint($0)
        //在這里添加處理返回特定code的邏輯
    })
    .addDisposableTo(disposeBag)
  • 還有提供過濾 一個范圍的code:
.filter(statusCodes: 200...300)
  • 一句話把請求到的數(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進行接下來的邏輯就可以了
    })
    .addDisposableTo(disposeBag)
  • 還可以mapString
.mapString(atKeyPath: "")

Moya + RxSwift + ObjectMapper

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

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

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

具體詳細用法可以看看項目說明噪生,這里默認已經(jīng)是有過Swift項目經(jīng)驗裆赵,使用過ObjectMapper,就不講解ObjectMapper的用法了杠园,因為如果寫出來顾瞪,這篇文章就太長了。如果你用的是HandyJson抛蚁,請看下一節(jié)的介紹陈醒。

接下來,我們把json解析為對象的過程也簡化一下瞧甩。

我們請求回來的數(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解析為一個單獨的User對象钉跷,也要能解析成一個User對象數(shù)組

ObjectMapper + RxSwift

Podfile中添加:

pod 'ObjectMapper', '~> 2.2'

然后記得pod install

接下來肚逸,創(chuàng)建RxMoyaMapper.swift文件
因為網(wǎng)絡(luò)請求和解析的過程中爷辙,會有各種錯誤出現(xiàn),首先要在文件中定義錯誤類型

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

然后定義一個RequestStatus的枚舉朦促,用來表示服務(wù)器返回給我們的code是不是約定的成功code膝晾,比方約定返回200為認證成功,401表示用戶登錄信息失效务冕,等等血当,那么我們是需要在返回認證不成功code時,進行一些邏輯處理的(比方彈出登錄框,讓用戶重新登錄)臊旭。

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

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

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

然后我們給RxSwift 的Observable添加extension离熏,兩個方法分別對應(yīng)解析為一個單獨的User對象佳谦,和解析成一個User對象數(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文件, 也提供好了完整的實現(xiàn)滋戳,可以現(xiàn)在項目看一下钻蔑,稍微修改一下就可以拿來在項目里使用。點擊跳轉(zhuǎn)

在項目里寫起來是這樣的:

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

//解析成User對象數(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

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

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

使用起來也是一模一樣的:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市申窘,隨后出現(xiàn)的幾起案子弯蚜,更是在濱河造成了極大的恐慌,老刑警劉巖剃法,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碎捺,死亡現(xiàn)場離奇詭異,居然都是意外死亡贷洲,警方通過查閱死者的電腦和手機收厨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來优构,“玉大人诵叁,你說我怎么就攤上這事∏胀郑” “怎么了拧额?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彪腔。 經(jīng)常有香客問我侥锦,道長,這世上最難降的妖魔是什么德挣? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任恭垦,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘署照。我一直安慰自己祸泪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布建芙。 她就那樣靜靜地躺著没隘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪禁荸。 梳的紋絲不亂的頭發(fā)上右蒲,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音赶熟,去河邊找鬼瑰妄。 笑死,一個胖子當(dāng)著我的面吹牛映砖,可吹牛的內(nèi)容都是我干的间坐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼邑退,長吁一口氣:“原來是場噩夢啊……” “哼竹宋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起地技,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蜈七,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莫矗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飒硅,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年作谚,在試婚紗的時候發(fā)現(xiàn)自己被綠了三娩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡食磕,死狀恐怖尽棕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情彬伦,我是刑警寧澤滔悉,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站单绑,受9級特大地震影響回官,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搂橙,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一歉提、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦苔巨、人聲如沸版扩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礁芦。三九已至,卻和暖如春悼尾,著一層夾襖步出監(jiān)牢的瞬間柿扣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工闺魏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留未状,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓析桥,卻偏偏與公主長得像司草,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烹骨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容