美文网首页
Realm 在 Swift 中配合值类型的尝试

Realm 在 Swift 中配合值类型的尝试

作者: 猴子的饼干 | 来源:发表于2020-04-20 15:32 被阅读0次

前言:前段时间做了一个纯 Swift 的产品。在数据缓存上,选择了 Realm。虽然基于SQLite 的 FMDB 也很香,因需求只是数据缓存,并没涉及到什么复杂的数据业务操作,且Realm的简单易用也打动了我。
看官说对了,我这不是什么时刻关注行业趋势,老夫就是喜新厌旧,垂涎隔壁Realm美色已久!今儿就把它办了。

注:缓存架构中与本文无关的,尽量屏蔽其对本文的干扰。

本文需要知识:

  • Swift 基础
  • Realm的基础操作
  • 开始

    Realm数据库,需要定义数据模型的模板,数据库会参照定的模板来进行缓存的操作,这里是不是和CoreData有点像?只不过 CoreData 是苹果封装好的可视化模型文件

    这里就引申出一个问题:

  1. Realm的模板是Class类型。
  2. 而在Swift项目中,苹果极力推荐使用值类型,例如Struct, Enum,Int等等。
    这意味着很多开发者的模型是Struct。

这就有点纠结了......
Swift香不香?香啊!
值类型香不香?香啊!
Realm香不香?中小型项目那是非常的香啊!

那能不能兼顾两者,使用Realm的同时,兼顾到Swift的值类型呢?

思考

最直接,最简单的实现的方案就是:

[ 让我们的值类型模型与Realm的模型模板互通 ]

如何互通?

使用临时的Json字符串充当数据交换的媒介

不增加程序在运行时的内存

Class 为引用类型,只要保证其不被持有,只在数据库缓存时生成,结束时释放,就像Json字符串一样充当个局部变量。这样就不会长时间占据程序内存。

Realm 模板 与 Struct 模型的互通

确定了使用了 Json字符串 充当媒介,那就让两者都遵循Json转换的方式,例如我的项目使用的是ObjectMapper

Struct(值类型)

Struct 遵循 ObjectMapper 的协议,例如一个首页Banner的模型:

struct HomeBannerModel:RJModelProtocol {

    var DBModelType: Object.Type = RLMHomeBannerModel.self
    
    var id:String = ""
    var imgPath:String = ""
    var theme:String = ""
    var type:String = ""
    var url:String = ""
    
    init?(map: Map) {
    }
    
    mutating func mapping(map: Map) {
        id <- map["id"]
        imgPath <- map["imgPath"]
        theme <- map["theme"]
        type <- map["type"]
        url <- map["url"]
    }
}

上方示例中的模型遵循的是 RJModelProtocol,且多了个DBModelType这样的变量。这是为了要彻底隔绝掉Realm的模板在开发中的影响,让值类型模型与Realm的Class转换在内部完成。

RJModelProtocol 的内容如下:

/// 供Swift中值类型实现
public protocol RJModelProtocol:Mappable {
    var DBModelType:Object.Type { get set }
}

协议遵循了ObjectMapper 的 Mappable 协议,并声明了了一个 Relam 模型类型的DBModelType供实现协议者实现,让开发者指定对应的 Realm 模板

Class(Realm模板)

首页Banner模型所对应的Realm模板如下:

class RLMHomeBannerModel: RJObject {

    @objc dynamic var id:String = ""
    @objc dynamic var imgPath:String = ""
    @objc dynamic var theme:String = ""
    @objc dynamic var type:String = ""
    @objc dynamic var url:String = ""
    
    override func mapping(map: Map) {
        id <- map["id"]
        imgPath <- map["imgPath"]
        theme <- map["theme"]
        type <- map["type"]
        url <- map["url"]
        rlm_id <- map["rlm_id"]
    }
}

RJObject只是封装了公用方法与协议,例如用于Json转换的Mappable,Realm的模板基类Object,以及模型的默认主键。

/// 供 RealmSwift 的模型继承
open class RJObject: Object, Mappable {
    required public init() {
        super.init()
    }

    required public init?(map: Map) {}
    open func mapping(map: Map) {}
    
    /// 默认主键,子类可重写
    @objc dynamic open var rlm_id:String = UUID().uuidString
    open override class func primaryKey() -> String? {
        return "rlm_id"
    }
}

至此,我们的值类型模型与Realm模板就都实现了Mappable协议,意味着两者都可以根据Json初始化,或转化为Json了。

利用Json实现数据互通

在数据缓存管理器中,我们直接传入遵循了 RJModelProtocol 协议的 Struct 的模型,在管理器内部转换为对应的 Realm 模板。

这里贴出其中的一个转换的方法:

/// 值类型转换为Realm模型
/// - Parameter objs: 符合RJModelProtocol协议的类型
/// - Returns: (转换结果,转换数据)
func rlm_transformRealmObjWithProtocol(objs:[RJModelProtocol]) -> (result:Bool, data:[Object]) {
    var models:[Object] = [Object]()
    for obj in objs {
        if let rlmObjc = obj.toRealmObject() {
            models.append(rlmObjc)
        } else {
            //"Data Erroo, Not a Realm Object"
            return (false, [Object]())
        }
    }
    return (true, models)
}

这个集合转换方法中涉及到了单个值类型转换为Realm模型Object,其实现如下:

let DBSpaceName:String? = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String
public extension RJModelProtocol {
    func toRealmObject() -> Object? {
        let json = self.toJSON()
        if let modelType = DBModelType as? Mappable.Type {
            if let obj = modelType.init(JSON: json) as? Object {
                guard let spaceName = DBSpaceName else {
                    //print("获取命名空间失败")
                    return obj
                }
                // 查询是否有关联对象列表
                for propertie in obj.objectSchema.properties {
                    if propertie.isArray {
                        let key = propertie.name
                        
                        /// 获取关联属性名
                        /// 获取关联属性类型名(需要命名域)
                        if let rlmClassName = propertie.objectClassName, let type = NSClassFromString(spaceName + "." + rlmClassName) as? Mappable.Type {
                            
                            // 获取关联属性列表数据
                            if let subJsonDataArray = json[key] as? [[String:Any]] {
                                var list = Array<Object>()
                                for subJsonData in subJsonDataArray {
                                    let value = type.init(JSON: subJsonData)
                                    if let rlmObj = value as? Object{
                                        list.append(rlmObj)
                                    }
                                }
                                obj.setValue(list, forKey: key)
                            }
                        }
                    }
                }
                return obj
            }
        }
        return nil
    }
}

可以看到,单个模型的转换是对RJModelProtocol协议扩展,让所有遵循这个协议的值类型都有了直接转换为Realm模板类型Object的能力。以此可以扩展出更丰富的功能。

类似的,Realm转换为Json可以将其键值提取出来,再转换成Json,提取键值的方法如下:

/// 转换为键值
func toDictionary() -> NSDictionary {
        let properties = self.objectSchema.properties.map { $0.name }
        let dicProps = self.dictionaryWithValues(forKeys: properties)

        let mutabledic = NSMutableDictionary()
        mutabledic.setValuesForKeys(dicProps)
        
        for prop in self.objectSchema.properties  {

            if prop.objectClassName != nil  {
                if let nestedObject = self[prop.name] as? Object {
                    mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
                } else if let nestedListObject = self[prop.name] as? ListBase {
                    var objects = [AnyObject]()
                    for index in 0 ..< nestedListObject._rlmArray.count  {
                        if let object = nestedListObject._rlmArray[index] as? Object {
                            objects.append(object.toDictionary())
                        }
                    }
                    
                    let key = NSString(string: prop.name)
                    mutabledic.setObject(objects, forKey: key)
                }
            }
        }
        return mutabledic
}

至此,我们就已经实现了值类型到Realm模板的引用类型的转换,剩下的就是实现缓存架构的其他部分了。
在本文的项目中,已经实现了使用Realm的缓存模块。

总结:
本文中的值类型配合Realm的理念很简单,使用Json作为胶水层方便两者通信。

在实际开发中,我们还可以对本文的尝试进行优化重构。例如将Realm模板动态生成,优化DBModelType的手动指定方式等,来极大的方便开发者在后续的开发中,彻底隔绝掉数据缓存模块的影响。

相关文章

  • Realm 在 Swift 中配合值类型的尝试

    前言:前段时间做了一个纯 Swift 的产品。在数据缓存上,选择了 Realm。虽然基于SQLite 的 FMDB...

  • Swift中的值类型和参照类型

    Swift中的值类型和参照类型 Swift中的值类型和参照类型

  • Swift值类型&引用类型

    Swift值类型&引用类型 前言 值类型和引用类型是Swift中两种数据存储方式,简单来说值类型就是直接存储的值,...

  • Swift 3 结构体

    swift中结构体,属于值类型,在swift中,Array, Dictionary, Set, Int, Floa...

  • Swift和OC中数组的区别

    swift:Array 在Swift 中是一个结构体,在Swift中结构体是值类型,他们的值是复制的而不是引用的。...

  • Swift5探究:值类型与引用类型

    值类型 在 Swift 中,struct、enum、 tuple 都是值类型。而平时使用的 Int, Double...

  • swift我们应该知道的

    1.class 和 struct 的区别? swift中,class是引用类型,struct是值类型。值类型在传递...

  • 进阶17 Swift2

    Swift中"?"和"!" 1.在swift中可选(optionals)类型,用"?"表示,用于处理值缺失的情况,...

  • iOS面试题

    1. struct和class的区别 swift中,class是引用类型,struct是值类型。值类型在传递和赋值...

  • 一些概念的理解

    1. struct和class的区别 swift中,class是引用类型,struct是值类型。值类型在传递和赋值...

网友评论

      本文标题:Realm 在 Swift 中配合值类型的尝试

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