美文网首页征服SwiftSwift开发iOS 第三方框架
Moya+Realm+RxSwift+SwiftyJSON优雅的

Moya+Realm+RxSwift+SwiftyJSON优雅的

作者: YxYYxY | 来源:发表于2017-04-04 23:26 被阅读562次

    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<T: Mapable>(_ jsonData: JSON, ModelType: T.Type) -> T? {
            return T(jsonData: jsonData)
        }
        
        /// 数据是JSON使用这个转
        func mapResponseToObj<T: Mapable>(_ type: T.Type) -> Observable<T?> {
            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<Moya.Response> {
            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, Any>?)->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<ApiManager>()
        
        func loadData<T: Mapable>(_ model: T.Type) -> Observable<T?> {
            return provider.XHOffLineCacheRequest(token: .github)
            .debug()//开启调试输出
            .distinctUntilChanged()//过滤相同的流
            .mapResponseToObj(T.self)
        }
    }
    

    最终在ViewControl里调用

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

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

    最后贴上完整Demo地址

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

    相关文章

      网友评论

      • struggle_LR:loadData()方法在主线程进行网络请求。应当异步进行请求,在主线程返回结果。所以 loadData() 方法应当加上 subscribeOn 和 observeOn
        ```
        func loadData<T: Mapable>(_ model: T.Type) -> Observable<T?> {
        return provider.XHOffLineCacheRequest(token: .github)
        .debug()
        .subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
        .observeOn(MainScheduler.instance)
        .distinctUntilChanged()
        .catchError({ (error) -> Observable<Response> in
        //捕获错误,不然离线访问会导致Binding error to UI,可以再此显示HUD等操作
        print(error.localizedDescription)
        return Observable.empty()
        })
        .mapResponseToObj(T.self)
        }
        ```
        这里由于涉及到线程的切换,所以ResultModel 实例在存入 realm 的时候和 crash,(realm 实例 及 Object 实例不支持多线程操作)
        YxYYxY:@struggle_LR 你好,感谢你提出的意见,githyb也已收到,抽空修改代码以免误导别人,再次感谢:smile:
      • kayakaya:请问Moya网络请求遇到401需要更新token,是怎么操作?
      • 等风来我就起飞:不明觉历
        小奉不在乎:大神可以留个联系方式私聊吗
        离线使用的时候这里报错了
        #if !RX_NO_MODULE

        func rxFatalError(_ lastMessage: String) -> Never {
        // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
        fatalError(lastMessage)
        }

        #endif


        NSLocalizedDescription=The Internet connection appears to be offline.}): file /Users/yangfengming/Desktop/zhianjia/Pods/RxCocoa/RxCocoa/RxCocoa.swift, line 154
        YxYYxY::joy: 学php去!

      本文标题:Moya+Realm+RxSwift+SwiftyJSON优雅的

      本文链接:https://www.haomeiwen.com/subject/dogzottx.html