RxSwift + Moya + HandyJSON + various plug-ins to build a responsive network architecture

Posted by sasi on Fri, 28 Jan 2022 02:27:08 +0100

🧚. RxSwift + Moya + HandyJSON + Plugins.👒👒👒

English |Simplified Chinese

Build responsive data binding network API architecture based on RxSwift + Moya

MoyaNetwork

The module is based on the network API architecture encapsulated by Moya

  • It is mainly divided into the following 8 parts:
    • NetworkConfig : set the configuration information at the beginning of the program, which is universal
      • addDebugging: whether to enable adding debugging plug-ins by default
      • baseURL: root path address
      • baseParameters: default basic parameters, such as userID, token, etc
      • baseMethod: default request type
      • updateBaseParametersWithValue: updates the default basic parameter data
    • RxMoyaProvider : add a response to the network request and return the Observable sequence
    • NetworkUtil : network correlation function
      • defaultPlugin: add a default plug-in
      • Transformapiobeservablejson: convert to an observable sequence JSON object
      • handyConfigurationPlugin: handle configuration plug-ins
    • PluginSubType : inherit and replace Moya plug-in protocol to facilitate subsequent extension
      • Configuration: after setting the network configuration information and before preparing the request, this method can be used to throw data directly when the local cache exists without executing subsequent network requests
      • lastNever: the return time of the last network response. This method can be used in scenarios such as key failure, re obtaining the key, and then automatically re requesting the network
    • NetworkAPI : add protocol attributes and encapsulate basic network requests on the basis of TargetType
      • ip: root path address
      • Parameters: request parameters
      • Plugins: array of plugins
      • Stub behavior: whether to use test data
      • retry: the number of retries when the request failed
      • Request: a network request method that returns an observable sequence JSON object
    • NetworkAPI+Ext : default implementation scheme of NetworkAPI protocol
    • NetworkAPIOO : object oriented converter, protocol oriented mode to object-oriented, convenient for small partners accustomed to OC thinking
      • cdy_ip: root path address
      • cdy_path: request path
      • cdy_parameters: request parameters
      • cdy_plugins: plug-ins
      • cdy_testJSON: test data
      • cdy_testTime: return time of test data, half a second by default
      • cdy_retry: the number of retries when the request failed
      • cdy_HTTPRequest: network request method
    • NetworkX : extension function method, etc
      • toJSON: object to JSON string
      • toDictionary: JSON string to dictionary
      • +=: Dictionary splicing

🎷 - Object oriented usage example 1:

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 usage example 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>()

    ///Configure and load animation plug-ins
    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 usage example 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) // The result is returned in the main thread
            .delay(.seconds(1), scheduler: MainScheduler.instance) // Return after 1 second delay
            .asDriver(onErrorJustReturn: []) // Return null at wrong time
    }
}

🎷 - Example 4 of chained request usage:

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("") // Exception throw
    }

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

🎷 - Batch request usage example 5:

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

    ///Configure and load animation plug-ins
    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

This module mainly encapsulates network related plug-ins based on moya

  • Six plug-ins have been packaged for your use:

🏠 - Simple to use, implement the protocol method in the API protocol, and then add the plug-in to it:

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

This module encapsulates network data analysis based on HandyJSON

  • It is roughly divided into the following three parts:
    • HandyDataModel : network outer data model
    • HandyJSONError : parsing error related
    • RxHandyJSON : HandyJSON data parsing. At present, two parsing schemes are provided
      • Scheme 1 - analyze the data in combination with the HandyDataModel model
      • Scheme 2 - parse the data of the specified key according to the keyPath. The precondition is that the data source must be in dictionary form

🎷 - Use examples in combination with network parts:

func request(_ count: Int) -> Driver<[CacheModel]> {
    CacheAPI.cache(count).request()
        .asObservable()
        .mapHandyJSON(HandyDataModel<[CacheModel]>.self)
        .compactMap { $0.data }
        .observe(on: MainScheduler.instance) // The result is returned in the main thread
        .delay(.seconds(1), scheduler: MainScheduler.instance) // Return after 1 second delay
        .asDriver(onErrorJustReturn: []) // Return null at wrong time
}

CocoaPods Install

Ex: Import network architecture API
- pod 'RxNetworks/MoyaNetwork'

Ex: Import data parsing
- pod 'RxNetworks/HandyJSON'

Ex: Import and load animation plug-ins
- pod 'RxNetworks/MoyaPlugins/Loading'

Demo

The general process is almost like this. The Demo is also written in detail. You can go and have a look by yourself 🎷

RxNetworksDemo

Tip: if you feel helpful, please point a star ⭐…

thank you. 🎇

  • There are relevant plug-ins in the following sequence, which will be added slowly

✌️

Topics: Swift iOS network architecture