美文网首页征服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

相关文章

  • Moya+Realm+RxSwift+SwiftyJSON优雅的

    Moya + RxSwift + SwiftyJSON + Realm 封装网络请求 先看一个例子,这段代码是请求...

  • 男人五十

    五十岁以后,开始变得优雅;从内而外的优雅。 优雅地赚钱,优雅地读书,优雅地和太太散步。看到邻里左右优雅...

  • 优雅的她

    优雅的你 优雅的我 比不上优雅的她 优雅的女王 优雅的公主 都比不上优雅的她 她 一个来自神秘国度的她 浑身散发着...

  • 咖啡,只是想象的情怀

    销售咖啡应该是卖的一种情怀吧? 优雅的环境,优雅的灯光,优雅的坐着。 优雅的音乐,优雅的钢琴声。...

  • 《优雅》| 做个优雅的女子

    晓雪写的这本《优雅》还是有很多实用技巧,女人可以从中受到生命的启发,学会如何将自己培养为一个有得体而精致的外表,丰...

  • 优雅如你——《刺猬的优雅》

    你如何定义优雅?高脚杯里摇晃的红酒,老CD里的华尔兹,华丽考究的衣着,妆容精致,每一件珠宝首饰都搭配得恰到好处。...

  • 比你看到的优雅

    现在这个年纪,喜欢优雅的女人,喜欢被人称做优雅,说别人优雅,却不那么容易。 优雅类似美丽,却雍容的多;优雅等...

  • 外国小学生优秀作文赏析:我的妈妈不优雅

    我的妈妈不优雅 李荷卿 编译 我的妈妈不像别的孩子的妈妈们那样优雅。 优雅妈妈们穿优雅的衣服,开优雅的汽车,而我的...

  • 你真懂?人间有味是清欢**努力邂逅优雅的你**带你精进

    摘记 /努力邂逅优雅的你***优雅君 文 /不详 努力邂逅优雅的你***优雅君 做了部分优化/整理/美化 前...

  • 爱一个人(二十四)

    有的人天生优雅,有的人懂得如何优雅。不知道月娘的优雅是否与生俱来,但是,月娘懂得如何的优雅,当然,优雅在粗鲁的人的...

网友评论

  • 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