基于Moya寇钉、RxSwift和ObjectMapper優(yōu)雅實(shí)現(xiàn)REST API請(qǐng)求

在Android開發(fā)中有非常強(qiáng)大的 Retrofit 請(qǐng)求命贴,結(jié)合RxJava可以非常方便實(shí)現(xiàn) RESTful API 網(wǎng)絡(luò)請(qǐng)求。在 iOS開發(fā)中也有非常強(qiáng)大的網(wǎng)絡(luò)請(qǐng)求庫(kù) Moya 萧豆,Moya是一個(gè)基于 Alamofire 開發(fā)的,輕量級(jí)的Swift網(wǎng)絡(luò)層。Moya的可擴(kuò)展性非常強(qiáng),可以方便和RXSwift昏名、ObjectMapper結(jié)合涮雷。

測(cè)試 REST API 定義

我們先用服務(wù)端定義幾個(gè)REST API,開發(fā)者根據(jù)自己的條件來(lái)實(shí)現(xiàn)葡粒。

請(qǐng)求錯(cuò)誤格式實(shí)例
{
    "error": "密碼錯(cuò)誤",
    "error_code": "password_error"
}
測(cè)試 API 列表
  1. http://127.0.0.1:8080/account/login份殿,參數(shù)username、password嗽交,post請(qǐng)求卿嘲,成功響應(yīng)為User。
  2. http://127.0.0.1:8080/user/{userId}夫壁,get請(qǐng)求拾枣,成功響應(yīng)為User。
  3. http://127.0.0.1:8080/user/query?q={keyword}盒让,get請(qǐng)求梅肤,成功響應(yīng)為User列表。

創(chuàng)建接口

// MyApiService.swift
import Moya

enum MyApiService {
    case login(username:String,password:String)
    case user(userId:String)
    case userQuery(keyword:String)
}
extension MyApiService:TargetType{
    // 定義請(qǐng)求的host
    var baseURL: URL {
        return URL(string: "http://127.0.0.1:8080")!
    }
    // 定義請(qǐng)求的路徑
    var path: String {
        switch self {
        case .login(_, _):
            return "/account/login"
        case .user(let userId):
            return "user/\(userId)"
        case .userQuery(_):
            return "user/query"
        }
    }
    // 定義接口請(qǐng)求方式
    var method: Moya.Method {
        switch self {
        case .login:
            return .post
        case .user,.userQuery:
            return .get
        }
    }
    // 定義模擬數(shù)據(jù)
    var sampleData: Data {
        switch self {
        case .login(let username, _):
            return "{\"username\": \"\(username)\", \"id\": 100}".data(using: String.Encoding.utf8)!
        case .user(_):
            return "{\"username\": \"Wiki\", \"id\": 100}".data(using: String.Encoding.utf8)!
        case .userQuery(_):
            return "{\"username\": \"Wiki\", \"id\": 100}".data(using: String.Encoding.utf8)!
        }
    }
    // 構(gòu)建參數(shù)
    var task: Task {
        switch self {
        case .login(let username, let passowrd):
            return .requestParameters(parameters: ["username": username,"passowrd": passowrd], encoding: URLEncoding.default)
        case .user(_):
            return .requestPlain
        case .userQuery(let keyword):
            return .requestParameters(parameters: ["keyword": keyword], encoding: URLEncoding.default)
        }
    }
    // 構(gòu)建請(qǐng)求頭部
    var headers: [String : String]? {
        return ["Content-type": "application/json"]
    }
}

請(qǐng)求數(shù)據(jù)

let provider = MoyaProvider<MyApiService>()

// Moya 提供最原始的請(qǐng)求方式邑茄,響應(yīng)的數(shù)據(jù)是二進(jìn)制
provider.request(.user(userId: "101")){ result in
        // do something with the result
        let text = String(bytes: result.value!.data, encoding: .utf8)
    print("text1 = \(text)")
}

// 結(jié)合RxSwift姨蝴,響應(yīng)的數(shù)據(jù)是二進(jìn)制
provider.rx.request(.user(userId: "101")).subscribe({result in
        // do something with the result
        switch result {
        case let .success(response):
            let text = String(bytes: response.data, encoding: .utf8)
            print("text2 = \(text)")
        case let .error(error):
            print(error)
    }
})

// 通過mapJSON把數(shù)據(jù)轉(zhuǎn)換成json格式
provider.rx.request(.user(userId: "101")).mapJSON().subscribe({result in
        // do something with the result
        switch result {
        case let .success(text):
            print("text3 = \(text)")
        case let .error(error):
            print(error)
    }
})
// 通過mapJSON把數(shù)據(jù)轉(zhuǎn)換成json格式,并轉(zhuǎn)換成最常見的Observable
provider.rx.request(.user(userId: "101")).mapJSON().asObservable().subscribe(onNext: { result in
        // do something with the result
        print("text4 = \(result)")
}, onError:{ error in
    // do something with the error
})
請(qǐng)求數(shù)據(jù):RxBlocking

RxBlocking使用教程 肺缕,可以使用同步的方式請(qǐng)求網(wǎng)絡(luò)

import RxBlocking

do{
    let text = try provider.rx.request(.user(userId: "101")).mapJSON().toBlocking().first()
    print("text5 = \(text)")
}catch{
    print(error)
}

結(jié)合 ObjectMapper

引入ObjectMapper
pod 'ObjectMapper', '~> 3.4'
編寫RxSwift拓展代碼
//  MoyaRxSwiftObjectMapperExtension.swift

import Foundation
import RxSwift
import Moya
import ObjectMapper

public extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
    func mapObject<T: BaseMappable>(type: T.Type) -> Single<T> {
        return self.map{ response in
            return try response.mapObject(type: type)
        }
    }
    func mapArray<T: BaseMappable>(type: T.Type) -> Single<[T]> {
        return self.map{ response in
            return try response.mapArray(type: type)
        }
    }
}
public extension ObservableType where E == Response {
    func mapObject<T: BaseMappable>(type: T.Type) -> Observable<T> {
        return self.map{ response in
            return try response.mapObject(type: type)
        }
    }
    func mapArray<T: BaseMappable>(type: T.Type) -> Observable<[T]> {
        return self.map{ response in
            return try response.mapArray(type: type)
        }
    }
}

public extension Response{
    func mapObject<T: BaseMappable>(type: T.Type) throws -> T{
        let text = String(bytes: self.data, encoding: .utf8)
        if self.statusCode < 400 {
            return Mapper<T>().map(JSONString: text!)!
        }
        do{
            let serviceError = Mapper<ServiceError>().map(JSONString: text!)
            throw serviceError!
        }catch{
            if error is ServiceError {
                throw error
            }
            let serviceError = ServiceError()
            serviceError.message = "服務(wù)器開小差左医,請(qǐng)稍后重試"
            serviceError.error_code = "parse_error"
            throw serviceError
        }
    }
    func mapArray<T: BaseMappable>(type: T.Type) throws -> [T]{
        let text = String(bytes: self.data, encoding: .utf8)
        if self.statusCode < 400 {
            return Mapper<T>().mapArray(JSONString: text!)!
        }
        do{
            let serviceError = Mapper<ServiceError>().map(JSONString: text!)
            throw serviceError!
        }catch{
            if error is ServiceError {
                throw error
            }
            let serviceError = ServiceError()
            serviceError.message = "服務(wù)器開小差授帕,請(qǐng)稍后重試"
            serviceError.error_code = "parse_error"
            throw serviceError
        }
    }
}
class ServiceError:Error,Mappable{
    var message:String = ""
    var error_code:String = ""
    required init?(map: Map) {}
    init() {
        
    }
    func mapping(map: Map) {
        error_code <- map["error_code"]
        message <- map["error"]
    }
    var localizedDescription: String{
        return message
    }
}

創(chuàng)建 User 類
//  User.swift
import ObjectMapper
class User: Mappable {
    required init?(map: Map) {}
    
    func mapping(map: Map) {
        userId <- map["userId"]
        name <- map["name"]
        age <- map["age"]
    }
    
    var userId:Int = 0
    var name:String = ""
    var age:Int = 0
}
測(cè)試
do{
    let user = try provider.rx.request(.user(userId: "101")).mapObject(type: User.self).toBlocking().first()
    print("user.name = \(user?.name)")
}catch{
    print(error)
}
do{
    let user = try provider.rx.request(.user(userId: "101")).asObservable().mapObject(type: User.self).toBlocking().first()
    print("user.name = \(user?.name)")
}catch{
    print(error)
}

do{
    let users = try provider.rx.request(.userQuery(keyword: "Wiki")).mapArray(type: User.self).toBlocking().first()
    print("test8 users.count = \(users?.count)")
}catch{
    if error is ServiceError {
        print((error as! ServiceError).message)
    }
    print(error)
}

打印日志

private func JSONResponseDataFormatter(_ data: Data) -> Data {
    do {
        let dataAsJSON = try JSONSerialization.jsonObject(with: data)
        let prettyData =  try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
        return prettyData
    } catch {
        return data // fallback to original data if it can't be serialized.
    }
}
let provider = MoyaProvider<MyApiService>(plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)])
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浮梢,隨后出現(xiàn)的幾起案子跛十,更是在濱河造成了極大的恐慌,老刑警劉巖秕硝,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芥映,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門后室,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人霎苗,你說(shuō)我怎么就攤上這事¢蛔觯” “怎么了唁盏?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)检眯。 經(jīng)常有香客問我厘擂,道長(zhǎng),這世上最難降的妖魔是什么锰瘸? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任刽严,我火速辦了婚禮,結(jié)果婚禮上避凝,老公的妹妹穿的比我還像新娘舞萄。我一直安慰自己,他們只是感情好管削,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布倒脓。 她就那樣靜靜地躺著,像睡著了一般含思。 火紅的嫁衣襯著肌膚如雪崎弃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天含潘,我揣著相機(jī)與錄音饲做,去河邊找鬼。 笑死遏弱,一個(gè)胖子當(dāng)著我的面吹牛盆均,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播漱逸,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼泪姨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼居砖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驴娃,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎循集,沒想到半個(gè)月后唇敞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咒彤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年疆柔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镶柱。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旷档,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歇拆,到底是詐尸還是另有隱情鞋屈,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布故觅,位于F島的核電站厂庇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏输吏。R本人自食惡果不足惜权旷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贯溅。 院中可真熱鬧拄氯,春花似錦、人聲如沸它浅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)罚缕。三九已至艇纺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邮弹,已是汗流浹背黔衡。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腌乡,地道東北人盟劫。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像与纽,于是被迫代替她去往敵國(guó)和親侣签。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塘装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • 去年有段時(shí)間得空,就把谷歌GAE的API權(quán)威指南看了一遍影所,收獲頗豐蹦肴,特別是在自己幾乎獨(dú)立開發(fā)了公司的云數(shù)據(jù)中心之后...
    騎單車的勛爵閱讀 20,544評(píng)論 0 41
  • 一說(shuō)到REST,我想大家的第一反應(yīng)就是“啊猴娩,就是那種前后臺(tái)通信方式阴幌。”但是在要求詳細(xì)講述它所提出的各個(gè)約束卷中,以及如...
    時(shí)待吾閱讀 3,432評(píng)論 0 19
  • 0 準(zhǔn)備 安裝注冊(cè)中心:Zookeeper矛双、Dubbox自帶的dubbo-registry-simple;安裝Du...
    七寸知架構(gòu)閱讀 13,995評(píng)論 0 88
  • API定義規(guī)范 本規(guī)范設(shè)計(jì)基于如下使用場(chǎng)景: 請(qǐng)求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請(qǐng)求頻率非常高蟆豫,建議使用雙通...
    有涯逐無(wú)涯閱讀 2,553評(píng)論 0 6
  • 巨啰嗦的框架:Paste + PasteDeploy + Routes + WebOb侦镇。后來(lái)OpenStack社區(qū)...
    Programmer客棧閱讀 677評(píng)論 0 0