Realm 在 Swift 中配合值类型的尝试
前言:前段时间做了一个纯 Swift 的产品。在数据缓存上,选择了 Realm。虽然基于SQLite 的 FMDB 也很香,因需求只是数据缓存,并没涉及到什么复杂的数据业务操作,且Realm的简单易用也打动了我。
看官说对了,我这不是什么时刻关注行业趋势,老夫就是喜新厌旧,垂涎隔壁Realm美色已久!今儿就把它办了。
注:缓存架构中与本文无关的,尽量屏蔽其对本文的干扰。
本文需要知识:
- Swift 基础
- Realm的基础操作
开始
Realm数据库,需要定义数据模型的模板,数据库会参照定的模板来进行缓存的操作,这里是不是和CoreData有点像?只不过 CoreData 是苹果封装好的可视化模型文件。
这里就引申出一个问题:
- Realm的模板是Class类型。
- 而在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的手动指定方式等,来极大的方便开发者在后续的开发中,彻底隔绝掉数据缓存模块的影响。