Moya+Realm+RxSwift+SwiftyJSON优雅的网络请求方式(扩展离线缓存)

Moya + RxSwift + SwiftyJSON + Realm 封装网络请求

先看一个例子,这段代码是请求数据然后展示在Label上

viewModel.loadData(IPModel.self)
            .map { $0?.city }
            .bindTo(self.showResult.rx.text)
            .addDisposableTo(disposeBag)

看起来是不是很优雅,接下来一步一步来详细解释是怎么实现的

先简单介绍一下用到的第三方库(具体用法请自己搜索)

  • Moya 一个网络抽象层库
  • Realm 一款支持运行在手机、平板和可穿戴设备上的嵌入式数据库(旨在取代CoreData和Sqlite)
  • RxSwift 响应式编程里超级优雅的框架
  • SwiftyJSON 强大的JSON转换库
  • RxCoCoa RxSwift对Cocoa的扩展

建立Moya的Target

/// 建立请求
enum ApiManager {
    case github
}

// MARK: - 实现Moya基本参数
extension ApiManager: TargetType {
    
    /// 基类API
    var baseURL: URL {
        return URL.init(string: "http://ditu.amap.com")!
    }
    
    /// 拼接请求路径
    var path: String {
        switch self {
        case .github:
            return "/service/regeo"
        }
    }
    
    /// 设置请求方式
    var method: Moya.Method {
        switch self {
        case .github:
            return .get
        }
    }
    
    /// 设置传参
    var parameters: [String: Any]? {
        switch self {
        case .github:
            return ["longitude" : "121.04925573429551", "latitude" : "31.315590522490712"]
        }
    }
    
    /// 设置编码方式
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// 这个用于测试,对此不太熟悉!
    var sampleData: Data {
        return "".data(using: String.Encoding.utf8)!
    }
    
    /// 设置任务的请求方式(可以改成上传upload、下载download)
    var task: Task {
        return .request
    }
    
    /// Alamofire中的验证,默认是false
    var validate: Bool {
        return false
    }
    
}

Extension+Observable

// MARK: - 扩展Map
extension Observable {
    /// 数据转JSON
    fileprivate func resultToJSON(_ jsonData: JSON, ModelType: T.Type) -> T? {
        return T(jsonData: jsonData)
    }
    
    /// 数据是JSON使用这个转
    func mapResponseToObj(_ type: T.Type) -> Observable {
        return map { representor in
            
            //检查是否是Moya.Response
            guard let response = representor as? Moya.Response else {
                throw XHError.XHNoMoyaResponse
            }
            
            //检查是否是一次成功的响应
            guard ((200...209) ~= response.statusCode) else {
                throw XHError.XHFailureHTTP
            }
            
            //将data转为JSON
            let json = JSON.init(data: response.data)
            
            //判断是否有状态码
            if let code = json[RESULT_CODE].string {
                
                //判断返回的状态码是否与成功状态码一致
                if code == XHStatus.XHSuccess.rawValue {
                    //将数据结构中的数据包字段转为JSON传出
                    return self.resultToJSON(json[RESULT_DATA], ModelType: type)
                }else {
                    //状态码与成功状态码不一致的时候,返回提示信息
                    throw XHError.XHMsgError(statusCode: json[RESULT_CODE].string, errorMsg: json[RESULT_MESSAGE].string)
                }
                
            }else {
                //报错非对象
                throw XHError.XHNotMakeObjectError
            }
            
        }
    }

Extension+RxMoyaProvider

extension RxMoyaProvider {
    func XHOffLineCacheRequest(token: Target) -> Observable {
        return Observable.create({[weak self] (observer) -> Disposable in
            //拼接成为数据库的key
            let key = token.baseURL.absoluteString + token.path + (self?.toJSONString(dict: token.parameters))!
            
            //建立Realm
            let realm = try! Realm()

            //设置过滤条件
            let pre = NSPredicate(format: "key = %@",key)
            
            //过滤出来的数据(为数组)
            let ewresponse = realm.objects(ResultModel.self).filter(pre)
            
            //先看有无缓存的话,如果有数据,数组即不为0
            if ewresponse.count != 0 {
                //因为设置了过滤条件,只会出现一个数据,直接取
                let filterResult = ewresponse[0]
                //重新创建成Response发送出去
                let creatResponse = Response(statusCode: filterResult.statuCode, data: filterResult.data!)
                observer.onNext(creatResponse)
            }
            
            
            //进行正常的网络请求
            let cancellableToken = self?.request(token) { result in
                switch result {
                case let .success(response):
                    observer.onNext(response)
                    observer.onCompleted()
                    //建立数据库模型并赋值
                    let model = ResultModel()
                    model.data = response.data
                    model.key = key
                    model.statuCode = response.statusCode
                    //写入数据库(注意:update参数,如果在设置模型的时候没有设置主键的话,这里是不能使用update参数的,update参数可以保证如果有相同主键的数据就直接更新数据而不是新建)
                    try! realm.write {
                        realm.add(model, update: true)
                    }
                case let .failure(error):
                    observer.onError(error)
                }
                
            }
            return Disposables.create {
                cancellableToken?.cancel()
            }
            
        })
    }
    
    
    /// 字典转JSON字符串(用于设置数据库key的唯一性)
    func toJSONString(dict:Dictionary?)->String{
        
        let data = try? JSONSerialization.data(withJSONObject: dict!, options: JSONSerialization.WritingOptions.prettyPrinted)
        
        let strJson = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
        
        return strJson! as String
        
    }
}

这里我存在数据库中使用的key是(baseurl + path + 参数的json字符串),这样可以保证每个请求地址的唯一性,避免同一接口不同参数的数据混淆,由于Moya的Response被final修饰了,不能继承,所以我是把Response中的属性单独提出来保存到数据库,需要的时候再从数据库中获取出来重新组成Response发送出去


这里我使用的是Realm默认创建的数据库,我有写一个方法来创建自定义名字的数据库,需要修改的才调用,Realm会自动建立默认数据库

  /// 配置数据库(如果不需要修改默认数据库的,就不调用这个方法)
  func creatDataBase() {
        //获取当前配置
        var config = Realm.Configuration()
        
        // 使用默认的目录,替换默认数据库
        config.fileURL = config.fileURL!.deletingLastPathComponent()
            .appendingPathComponent(cacheDatabaseName)
        
        // 将这个配置应用到默认的 Realm 数据库当中
        Realm.Configuration.defaultConfiguration = config
    } 

建立数据模型

这是服务器返回的数据建立的模型(我只写了一个字段做测试)

  • 先创建协议
/// 定义数据转JSON协议
public protocol Mapable {
    init?(jsonData:JSON)
}
  • 建立数据模型
struct IPModel: Mapable {
    let city: String?
    
    init?(jsonData: JSON) {
        self.city = jsonData["city"].string
    }  
}

建立ViewModel

这里将RxMoya默认的Request改成刚刚我自己写的Extension的方法

class ViewModel {
    private let provider = RxMoyaProvider()
    
  

    func loadData(_ model: T.Type) -> Observable {
          return provider.XHOffLineCacheRequest(token: .github)
                                 .debug()
                                 .subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: 
                                 .default))
                                 .observeOn(MainScheduler.instance)
                                 .distinctUntilChanged()
                                 .catchError({ (error) -> Observable in
                                 //捕获错误,不然离线访问会导致Binding error to UI,可以再此显示HUD等操作
                                       print(error.localizedDescription)
                                       return Observable.empty()
                                   })
                                  .mapResponseToObj(T.self)
                 }
}

最终在ViewControl里调用

viewModel.loadData(IPModel.self)
            .map { $0?.city }
            .bindTo(self.showResult.rx.text)
            .addDisposableTo(disposeBag)

写这个小封装看了不少大神的博客资料等,特别感谢前人们的博客资料!
因为没存地址,这里就不贴链接了,以后找到了贴上来!
第一次分享,希望大家指点其中的错误以及不足!万分感谢!

最后贴上完整Demo地址

觉得不错的求给个Star,欢迎提Issues

你可能感兴趣的:(Moya+Realm+RxSwift+SwiftyJSON优雅的网络请求方式(扩展离线缓存))