RxSwift + Moya + HandyJSON + 各種插件搭建響應(yīng)式網(wǎng)絡(luò)架構(gòu)

<font color=red>??. RxSwift + Moya + HandyJSON + Plugins.??????</font>


English | 簡體中文

基于 RxSwift + Moya 搭建響應(yīng)式數(shù)據(jù)綁定網(wǎng)絡(luò)API架構(gòu)

MoyaNetwork

該模塊是基于Moya封裝的網(wǎng)絡(luò)API架構(gòu)

  • 主要分為以下8部分:
    • NetworkConfig:在程序最開始處設(shè)置配置信息,全局通用
      • addDebugging:是否開啟默認(rèn)加入調(diào)試插件
      • baseURL:根路徑地址
      • baseParameters:默認(rèn)基本參數(shù),類似:userID淮腾,token等
      • baseMethod:默認(rèn)請求類型
      • updateBaseParametersWithValue:更新默認(rèn)基本參數(shù)數(shù)據(jù)
    • RxMoyaProvider:對網(wǎng)絡(luò)請求添加響應(yīng)式冷溃,返回Observable序列
    • NetworkUtil:網(wǎng)絡(luò)相關(guān)函數(shù)
      • defaultPlugin:添加默認(rèn)插件
      • transformAPIObservableJSON:轉(zhuǎn)換成可觀察序列JSON對象
      • handyConfigurationPlugin:處理配置插件
    • PluginSubType:繼承替換Moya插件協(xié)議聊训,方便后序擴(kuò)展
      • configuration:設(shè)置網(wǎng)絡(luò)配置信息之后绿饵,開始準(zhǔn)備請求之前灿里,該方法可以用于本地緩存存在時(shí)直接拋出數(shù)據(jù)而不用再執(zhí)行后序網(wǎng)絡(luò)請求等場景
      • lastNever:最后的最后網(wǎng)絡(luò)響應(yīng)返回時(shí)刻凿叠,該方法可以用于密鑰失效重新去獲取密鑰然后自動(dòng)再次網(wǎng)絡(luò)請求等場景
    • NetworkAPI:在TargetType基礎(chǔ)上增加協(xié)議屬性和封裝基礎(chǔ)網(wǎng)絡(luò)請求
      • ip:根路徑地址
      • parameters:請求參數(shù)
      • plugins:插件數(shù)組
      • stubBehavior:是否走測試數(shù)據(jù)
      • retry:請求失敗重試次數(shù)
      • request:網(wǎng)絡(luò)請求方法,返回可觀察序列JSON對象
    • NetworkAPI+ExtNetworkAPI協(xié)議默認(rèn)實(shí)現(xiàn)方案
    • NetworkAPIOO:面向?qū)ο筠D(zhuǎn)換器昆淡,面向協(xié)議模式轉(zhuǎn)面向?qū)ο竺倘常奖懔?xí)慣OC思維的小伙伴
      • cdy_ip:根路徑地址
      • cdy_path:請求路徑
      • cdy_parameters:請求參數(shù)
      • cdy_plugins:插件
      • cdy_testJSON:測試數(shù)據(jù)
      • cdy_testTime:測試數(shù)據(jù)返回時(shí)間,默認(rèn)半秒
      • cdy_retry:請求失敗重試次數(shù)
      • cdy_HTTPRequest:網(wǎng)絡(luò)請求方法
    • NetworkX:擴(kuò)展函數(shù)方法等
      • toJSON:對象轉(zhuǎn)JSON字符串
      • toDictionary:JSON字符串轉(zhuǎn)字典
      • +=:字典拼接

?? - 面向?qū)ο笫褂檬纠?:

class OOViewModel: NSObject {
    let disposeBag = DisposeBag()
    let data = PublishRelay<String>()

    func loadData() {
        var api = NetworkAPIOO.init()
        api.cdy_ip = "https://www.httpbin.org"
        api.cdy_path = "/ip"
        api.cdy_method = .get
        api.cdy_plugins = [NetworkLoadingPlugin.init()]
        api.cdy_retry = 3

        api.cdy_HTTPRequest()
            .asObservable()
            .compactMap{ (($0 as! NSDictionary)["origin"] as? String) }
            .catchAndReturn("")
            .bind(to: data)
            .disposed(by: disposeBag)
    }
}

?? - MVP使用示例2:

enum LoadingAPI {
    case test2(String)
}

extension LoadingAPI: NetworkAPI {
    var ip: APIHost {
        return NetworkConfig.baseURL
    }

    var path: String {
        return "/post"
    }

    var parameters: APIParameters? {
        switch self {
        case .test2(let string): return ["key": string]
        }
    }
}

class LoadingViewModel: NSObject {
    let disposeBag = DisposeBag()
    let data = PublishRelay<NSDictionary>()

    /// 配置加載動(dòng)畫插件
    let APIProvider: MoyaProvider<MultiTarget> = {
        let configuration = URLSessionConfiguration.default
        configuration.headers = .default
        configuration.timeoutIntervalForRequest = 30
        let session = Moya.Session(configuration: configuration, startRequestsImmediately: false)
        let loading = NetworkLoadingPlugin.init()
        return MoyaProvider<MultiTarget>(session: session, plugins: [loading])
    }()

    func loadData() {
        APIProvider.rx.request(api: LoadingAPI.test2("666"))
            .asObservable()
            .subscribe { [weak self] (event) in
                if let dict = event.element as? NSDictionary {
                    self?.data.accept(dict)
                }
            }.disposed(by: disposeBag)
    }
}

?? - MVVM使用示例3:

class CacheViewModel: NSObject {
    let disposeBag = DisposeBag()
    struct Input {
        let count: Int
    }
    struct Output {
        let items: Driver<[CacheModel]>
    }

    func transform(input: Input) -> Output {
        let elements = BehaviorRelay<[CacheModel]>(value: [])
        let output = Output(items: elements.asDriver())
        request(input.count)
            .asObservable()
            .bind(to: elements)
            .disposed(by: disposeBag)
            
        return output
    }
}

extension CacheViewModel {
    func request(_ count: Int) -> Driver<[CacheModel]> {
        CacheAPI.cache(count).request()
            .asObservable()
            .mapHandyJSON(HandyDataModel<[CacheModel]>.self)
            .compactMap { $0.data }
            .observe(on: MainScheduler.instance) // 結(jié)果在主線程返回
            .delay(.seconds(1), scheduler: MainScheduler.instance) // 延時(shí)1秒返回
            .asDriver(onErrorJustReturn: []) // 錯(cuò)誤時(shí)刻返回空
    }
}

?? - 鏈?zhǔn)秸埱笫褂檬纠?:

class ChainViewModel: NSObject {
    let disposeBag = DisposeBag()
    let data = PublishRelay<NSDictionary>()

    func chainLoad() {
        requestIP()
            .flatMapLatest(requestData)
            .subscribe(onNext: { [weak self] data in
                self?.data.accept(data)
            }, onError: {
                print("Network Failed: \($0)")
            }).disposed(by: disposeBag)
    }
}

extension ChainViewModel {
    func requestIP() -> Observable<String> {
        return ChainAPI.test.request()
            .asObservable()
            .map { ($0 as! NSDictionary)["origin"] as! String }
            .catchAndReturn("") // 異常拋出
    }

    func requestData(_ ip: String) -> Observable<NSDictionary> {
        return ChainAPI.test2(ip).request()
            .asObservable()
            .map { ($0 as! NSDictionary) }
            .catchAndReturn(["data": "nil"])
    }
}

?? - 批量請求使用示例5:

class BatchViewModel: NSObject {
    let disposeBag = DisposeBag()
    let data = PublishRelay<NSDictionary>()

    /// 配置加載動(dòng)畫插件
    let APIProvider: MoyaProvider<MultiTarget> = {
        let configuration = URLSessionConfiguration.default
        configuration.headers = .default
        configuration.timeoutIntervalForRequest = 30
        let session = Moya.Session(configuration: configuration, startRequestsImmediately: false)
        let loading = NetworkLoadingPlugin.init()
        return MoyaProvider<MultiTarget>(session: session, plugins: [loading])
    }()

    func batchLoad() {
        Observable.zip(
            APIProvider.rx.request(api: BatchAPI.test).asObservable(),
            APIProvider.rx.request(api: BatchAPI.test2("666")).asObservable(),
            APIProvider.rx.request(api: BatchAPI.test3).asObservable()
        ).subscribe(onNext: { [weak self] in
            guard var data1 = $0 as? Dictionary<String, Any>,
                  let data2 = $1 as? Dictionary<String, Any>,
                  let data3 = $2 as? Dictionary<String, Any> else {
                      return
                  }
            data1 += data2
            data1 += data3
            self?.data.accept(data1)
        }, onError: {
            print("Network Failed: \($0)")
        }).disposed(by: disposeBag)
    }
}

MoyaPlugins

該模塊主要就是基于moya封裝網(wǎng)絡(luò)相關(guān)插件

  • 目前已封裝6款插件供您使用:
    • Cache:網(wǎng)絡(luò)數(shù)據(jù)緩存插件
    • Loading:加載動(dòng)畫插件
    • Indicator:指示器插件
    • Warning:網(wǎng)絡(luò)失敗提示插件
    • Debugging:調(diào)試日志插件
    • GZip:解壓縮插件

?? - 簡單使用昂灵,在API協(xié)議當(dāng)中實(shí)現(xiàn)該協(xié)議方法避凝,然后將插件加入其中即可:

var plugins: APIPlugins {
    let cache = NetworkCachePlugin(cacheType: .networkElseCache)
    let loading = NetworkLoadingPlugin.init(delayHideHUD: 0.5)
    loading.changeHudCallback = { (hud) in
        hud.detailsLabel.textColor = UIColor.yellow
    }
    return [loading, cache]
}

HandyJSON

該模塊是基于HandyJSON封裝網(wǎng)絡(luò)數(shù)據(jù)解析

  • 大致分為以下3個(gè)部分:
    • HandyDataModel:網(wǎng)絡(luò)外層數(shù)據(jù)模型
    • HandyJSONError:解析錯(cuò)誤相關(guān)
    • RxHandyJSON:HandyJSON數(shù)據(jù)解析,目前提供兩種解析方案
      • 方案1 - 結(jié)合HandyDataModel模型使用解析出data數(shù)據(jù)
      • 方案2 - 根據(jù)keyPath解析出指定key的數(shù)據(jù)眨补,前提條件數(shù)據(jù)源必須字典形式

?? - 結(jié)合網(wǎng)絡(luò)部分使用示例:

func request(_ count: Int) -> Driver<[CacheModel]> {
    CacheAPI.cache(count).request()
        .asObservable()
        .mapHandyJSON(HandyDataModel<[CacheModel]>.self)
        .compactMap { $0.data }
        .observe(on: MainScheduler.instance) // 結(jié)果在主線程返回
        .delay(.seconds(1), scheduler: MainScheduler.instance) // 延時(shí)1秒返回
        .asDriver(onErrorJustReturn: []) // 錯(cuò)誤時(shí)刻返回空
}

CocoaPods Install

Ex: 導(dǎo)入網(wǎng)絡(luò)架構(gòu)API
- pod 'RxNetworks/MoyaNetwork'

Ex: 導(dǎo)入數(shù)據(jù)解析
- pod 'RxNetworks/HandyJSON'

Ex: 導(dǎo)入加載動(dòng)畫插件
- pod 'RxNetworks/MoyaPlugins/Loading'

Demo

大體流程差不多就是這樣管削,Demo也寫的很詳細(xì),大家可以自己去看看??

RxNetworksDemo

提示:如果覺得有幫助撑螺,請幫忙點(diǎn)個(gè)星 ?..

謝謝.??

  • 后序有相關(guān)插件含思,也會慢慢補(bǔ)充..

??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市甘晤,隨后出現(xiàn)的幾起案子含潘,更是在濱河造成了極大的恐慌,老刑警劉巖安皱,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调鬓,死亡現(xiàn)場離奇詭異艇炎,居然都是意外死亡酌伊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來居砖,“玉大人虹脯,你說我怎么就攤上這事∽嗪颍” “怎么了循集?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔗草。 經(jīng)常有香客問我咒彤,道長,這世上最難降的妖魔是什么咒精? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任镶柱,我火速辦了婚禮,結(jié)果婚禮上模叙,老公的妹妹穿的比我還像新娘歇拆。我一直安慰自己,他們只是感情好范咨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布故觅。 她就那樣靜靜地躺著,像睡著了一般渠啊。 火紅的嫁衣襯著肌膚如雪输吏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天替蛉,我揣著相機(jī)與錄音评也,去河邊找鬼。 笑死灭返,一個(gè)胖子當(dāng)著我的面吹牛盗迟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熙含,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼罚缕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怎静?” 一聲冷哼從身側(cè)響起邮弹,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚓聘,沒想到半個(gè)月后腌乡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夜牡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年与纽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡急迂,死狀恐怖影所,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情僚碎,我是刑警寧澤猴娩,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站勺阐,受9級特大地震影響卷中,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渊抽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一仓坞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腰吟,春花似錦无埃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灵疮,卻和暖如春织阅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背震捣。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工荔棉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒿赢。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓润樱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親羡棵。 傳聞我的和親對象是個(gè)殘疾皇子壹若,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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