Realm.Object与NSObject的转换中swift协议妙用的体现

发表于 4年以前  | 总阅读数:1572 次

概述

简单介绍一下,Realm数据库:

Realm 是一个跨平台的移动数据库引擎,于 2014 年 7 月发布,准确来说,它是专门为移动应用所设计的数据持久化解决方案之一。优点主体现在:1.跨平台 2.简单易用,并且相对于iOS中使用的SQLite 以及 Core Data 要快很多 3.可视化

在项目中由于使用到了 Realm数据库,就避免不了创建Realm数据库特有的数据库模型:基本类型为Realm.Object的模型。之后下文都将使用Realm.Object代表realm数据模型。

由于Realm.Object的特殊性,它不可跨线程使用,特有的list等基础类型,以及为了与业务关系解耦等原因,业务中不直接使用Realm.Object。所以我们给每个Realm.Object模型,创建了一个对应的业务模型下文简称为模型

如图1,我们需要在两种对应模型之间进行转换。为了减少重复的代码,就需要写一个模型转换功能。

想到要写一个模型转换功能?我们应该能想到有很多 Dictionary或者Array 与模型互转的框架,这些是开发中很常见的功能。这里不在赘述他们的原理,毕竟OC,swift中的转换思路和现有库,想必各位读者应该都看过或者写过不少。

简单描述一下已有框架的使用流程,如果从A类模型转换成B类模型。利用这些框架,我们想到的应该是 A类模型先转换成Dictionary或者json,然后Dictionary或者json转换成B类模型,使用已有框架基本都是这种套路。

本文章要阐述的不是如何使用这些框架,也不是单纯讲转换原理,而是采用更加swift的方式来实现转换功能。至于为什么要"多此一举"自己写转换具体有如下原因。

  • swift转换框架大部分都是使用的OC原有Api思路来转换,换汤不换药,虽然还是有一个比较特殊的直接获取指针赋值的,但是在当时(大概两年前)又不够稳定
  • 我们有Realm.Object这种类型的模型,它有自定义的基本类型,目前已有框架是没法直接使用来进行转换的。不过他提供了Dictionary生成Realm.Object的方法(这个方法还有待改进),可惜他没有提供Realm.Object生成Dictionary的方法
  • 上面我提过了已有套路实现转换要分两步,能不能有方式可以直接Realm.Object模型互转?
  • 实现转换的方式过程中少用或者尽量不用OC的原有运行时,符号类型判断,对象动态创建等,尽量使用swift官方标配protocol来做这件事

上面的原因条件都说了,那就开始准备咯。既然自己造轮子,那当然得造出适合自己的轮子。在这转换的实现过程中swift协议起到了不可或缺的作用,希望现在分享出来也不算太晚。正文开始咯~

转换之前的准备

概述中已经介绍了写这转换功能的初衷,我们要做的Realm.Object与普通业务模型转换。在此先列下写这转换时的想法:

  • 你要对Realm.Object有足够的了解,充分利用Realm.Object的已有特性
  • 转换方式尽量使用swift特性
  • 能不能直接由普通类型模型转换成Realm.Object,减少中间的Dictionary步骤?

要实现这个转换主要涉及到的具体问题

  • swift 类或结构体 Any.Type 创建对象(包括NSObject,Option,Array,Dictionary等泛型类型)
  • Realm List<T> 类型与 [T] 类型通用转换
  • RealmObject类型属性遍历(需要翻看realm源码,因为官方文档其实也没有提及,我也不带大家看源码了,有兴趣可以私我分享realm,哈哈哈)以及NSObject类型属性遍历方式

从基础代码开始

首先为了功能,我们定义协议,并写好转换方法名称。这样业务模型(需继承NSObject,实际赋值那步需要使用KVC)遵循Persistable协议后,就可以使用了

protocol Persistable {

  associatedtype ManagedObject: RealmSwift.Object

  //业务模型转RealmObject方法

  public func managedObject() -> ManagedObject

  //RealmObject转业务模型方法

  public static func modelInstance(managedObject:RealmSwift.Object) -> Self
}

Realm.Object 转换成 普通业务模型

接下来我们先完成public static func modelInstance(managedObject:RealmSwift.Object) -> Self的默认实现。

实现如下

extension Persistable where Self:NSObject {

    public static func modelInstance(managedObject:RealmSwift.Object) -> Self {

            var instance:Self

            instance = Self._creatInstance()

            AutoExchangeHelper.updateModelWithObject(model: instance , managedObject: managedObject)

            return instance

    }

}

以上协议实现分两步:

  • 创建业务模型
  • 遍历业务模型属性进行赋值

第一步如何创建对象?想一想,在协议类中我们不知道该具体类型,该如何创建对象?在该协议的扩展中,我们知道遵循该协议的是NSObject类型,而只要是继承NSObject类型其实都可以使用init方法创建对象。比较简单的方案如下,给NSObject类型添加扩展。可能有点难想到

extension NSObject  {

    static func _creatInstance() -> Self {

        return  self.init()

    }

}

第二步 遍历普通业务模型。这个大家应该比较熟悉,runtime相关api,想必大家已了然于胸。在swift我们可以使用Mirror来遍历model属性,得到属性的类型Any.Type,然后判断类型继续操作。我们看下面代码

extension AutoExchangeHelper {

       static func updateModelWithObject<T:NSObject>(model:T,managedObject:Object)  {

            Mirror(reflecting: model).children.forEach { (child) in

                guard let name = child.label else{

                    return

                }

                //realm的scheme 包含属性的所有信息,可以用来跟我们的Mirror遍历的类型进行对比容错。

                let scheme =  managedObject.objectSchema

                let propertype = scheme[name].type // Realm.Object对象对应属性类型

                let modelType:Any.Type = type(of: child.value)//业务模型对象属性对应类型

                let objectValue = managedObject[name] //数据库模型对应属性值

                if propertype == RLMPropertyType.object {

                   //包含对象 以及数组(数组内容非基础数据类型)

                   if 对象 {

                       ...

                   }else if 集合 {

                       ...

                   }

                }else

                {

                 //包含 String ,Int ,List<String> List<Int> 等

                }

            }

        }

}

上述代码得到了 modelType:Any.Type 类型,接下来的思路如下。已知的有model(业务模型)

判断属性类型的具体类型

  • 如果是基础类型直接使用setValue:forKey赋值,
  • 如果是非基础类型,我们需要创建对应类型的对象(递归给其属性赋值),然后使用setValue:forKey赋值

属性是基础类型的情况

realm的RLMPropertyType判断出的基础类型 有 String ,Int ,List<String>, List<Int>

  1. 属性类型是IntString,Date等类型直接使用 setValue:forKey赋值
  2. 属性类型是List<String>,List<Int>等类型

现在我们要想办法 将List<T> 转换成 [T] ( T 是基础类型) 先不着急傻瓜式的,一句句判断类型。我们分析下。List<T>是realm中的泛型集合结构体,跟数组类似,我们可以通过给List<T>添加扩展方法来转成对应的[T]类型

结合 1和2的情况 代码如下

private protocol _BaseValueProtocol {

    var _snsBaseValue:Any? { get}

}

extension List:_BaseValueProtocol {

    var _snsBaseValue: Any? {

        var values = [Element]()

        forEach { (value) in

            values.append(value)

        }

        return values

    }

}

基础部分实现代码 如下

     if propertype == RLMPropertyType.object {

            //包含对象 以及数组(数组内容非基础数据类型)

    }

    else{

            //objectValue 是 String ,Int ,List<String> List<Int> 等基础类型或者集合元素是基础类型的类型

            if let realVaule = (objectValue as? _BaseValueProtocol)?._snsBaseValue {

                        model.setValue(realVaule, forKey: name)

             }else{

                        model.setValue(objectValue, forKey: name)

            }

    }

属性是非基础类型

1.modelType:Any.TypeNSObject类型,我们可以利用给NSObject添加的扩展创建对象

先修改一下我们的NSObject的扩展

//createInstance

private protocol _AutoMallocObjectProtocol {

    static func _creatInstance() -> Self

}

//MARK: - 实现协议

extension NSObject:_AutoMallocObjectProtocol  {

    static func _creatInstance() -> Self {

        return  self.init()

    }

}





创建代码

if propertype == RLMPropertyType.object {

     //包含对象 以及数组(数组内容非基础数据类型)

    if 对象 {

        let subm = (modelType:Any.Type as? _AutoMallocObjectProtocol.Type)?._creatInstance() 

        ... //subm 递归调用当前方法给其属性赋值

        model.setValue(subm, forKey: name)

     }else if 集合 {

        ...

    }

}

2.modelType:Any.Type[T] ,(Array\<Element>)等泛型集合类型

如何通过Any.Type类型创建此泛型集合呢

很明显,swift数组类型不是NSObject类型,无法使用我们之前的扩展实现,它其实是个带泛型的struct类型,所以我们如果要创建对应数组必须知道该元素类型进行创建。我们可以让swift数组类型遵循我们的 _AutoMallocObjectProtocol 协议 并添加默认实现 如下

extension Collection {

    static func _collectionCreatInstance() -> [Iterator.Element] {

        let instance = [Iterator.Element]()

        return instance

    }

}



//MARK: 集合类型

extension Array:_AutoMallocObjectProtocol {

    static func _creatInstance() -> Array<Element> {

        return _collectionCreatInstance()

    }

}



extension Set:_AutoMallocObjectProtocol {

    static func _creatInstance() -> Set<Element> {

        let arr = _collectionCreatInstance()

        return Set(arr)

    }

}



以上代码 顺便将set类型也遵循了创建协议并实现了,是一样的。所以这样我们可以通过下面代码来创建泛型集合啦

if propertype == RLMPropertyType.object {

     //包含对象 以及数组(数组内容非基础数据类型)

    if 对象 {

        ...

     }else if 集合 {

        let collections = (modelType:Any.Type as? _AutoMallocObjectProtocol.Type)?._creatInstance()

        //collections 中的该如何创建请继续看

    }
}

继续思考接下来做什么

  • 我们要创建这个数组里面的元素对象,并给元素对象的所有属性赋值(通过递归)
  • 将这些元素对象加到 collections中。这样此collection对象,它的内容也饱满了

到了这里我们又可以想一想问题,我们现在知道数组类型 modelType:Any.Type 如何创建数组的元素对象?,我们需要得到数组元素的类型,怎么办呢?这个时候脑袋里蹦出四个字 扩展、协议。 我们定义一个创建元素对象的协议,因为只有泛型集合内部才知道其Element类型,所以让泛型集合遵循并实现。

//MARK:创建嵌套类型中的Instance

private protocol _MallcoContentInstanceProtocol  {

    static func _creatElement() -> Any?

}



//MARK: 集合类型

extension Array:_AutoMallocObjectProtocol,_MallcoContentInstanceProtocol {

    static func _creatInstance() -> Array<Element> {

        return _collectionCreatInstance()

    }



    static func _creatElement() -> Any? {

        if let _type = Element.self as? _AutoMallocObjectProtocol.Type {

            return  _type._creatInstance() as? Element

        }

        return nil

    }

    

}



extension Set:_AutoMallocObjectProtocol,_MallcoContentInstanceProtocol {

    static func _creatInstance() -> Set<Element> {

        let arr = _collectionCreatInstance()

        return Set(arr)

    }

    

    static func _creatElement() -> Any? {

        if let _type = Element.self as? _AutoMallocObjectProtocol.Type {

            return  _type._creatInstance() as? Element

        }

        return nil

    }

}



这样以来我们就可以通过如下代码创建数组中的元素类型对象了

if propertype == RLMPropertyType.object {

     //包含对象 以及数组(数组内容非基础数据类型)

    if 对象 {

        ...

     }else if 集合 {

        var collections = (modelType:Any.Type as? _AutoMallocObjectProtocol.Type)?._creatInstance()

        for  m in objectValue {

            if let m = (modelType:Any.Type as? _MallcoContentInstanceProtocol.Type)?._creatElement()

            ... //m 进行递归kvc赋值

            collections.append(m)

        }

        model.setValue(collections, forKey: name)

    }

}



泛型集合类型的创建问题差不多就解决了。

  1. modelType:Any.Type[T]? ,(Array\<Element>?)等可选非基本类型

到这里不知道有没有发现一个问题。上述集合类型,或者数组元素中类型 可能不是NSObject,也不是集合类型,而是可选类型的非基本类型。

eg. modelType:Any.Type 类型是 [T]?也就是(Option<Array<T>>),其中T可能是个可选类型.

这个时候我们会发现可选类型并不遵循协议 就无法创建对象。怎么办?

按照上面的套路,我们继续给可选类型添加相关协议,并实现。

//MARK: 可选类型

extension Optional:_AutoMallocObjectProtocol,_MallcoContentInstanceProtocol {

    

    static func _creatInstance() -> Optional<Wrapped> {

        var instance:Wrapped

        if let _type = Wrapped.self as? _AutoMallocObjectProtocol.Type {

            instance = _type._creatInstance() as! Wrapped

            return Optional.init(instance)

        }

        return nil

    }

    

    static func _creatElement() -> Any? {

        if let _type = Wrapped.self as? _MallcoContentInstanceProtocol.Type {

            return  _type._creatElement()

        }

        return nil

    }

}

对于 modelType:Any.Type属于NSObject或者泛型组合类型的情况。我们已经解决的差不多了。

总结

上面讲了Realm.Object 转换成普通业务模型的实现过程,反过来普通业务模型 转换成 Realm.Object的实现与上面虽然有所区别,不过类似,就不在多说。

在当时写这转换的时候,所想的是方式方法都想要利用swift特性,这也是我想要说明的部分,这篇转换其实还有可以优化的地方,优化部分有兴趣可以联系我。

在整篇文章中我们所用到的转换协议Persistable,创建对象协议_AutoMallocObjectProtocol,创建嵌套类型中元素协议_MallcoContentInstanceProtocol。其实就是swift面向协议编程protocol+extension的具体体现。通过协议+扩展实现一个功能,能够定义所需要的充分必要条件,不多也不少。

swift相对于OC的传统协议,虽说只是多了一个协议扩展的特性,但却带来了编程范式的进化。实际上,Swift的标准库几乎是everything is starting out as a protocol。这也是为什么我想到了协议来做这些事情的原因。

作者 | 狐友技术团队
来源 | 搜狐技术产品

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/fQxVGCqHp9ndFN9-Kkmcjw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:7月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:7月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:7月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:7月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:7月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:7月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:7月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:7月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:7月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:7月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:7月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:7月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:7月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:7月以前  |  398次阅读  |  详细内容 »
 相关文章
快速配置 Sign In with Apple 4年以前  |  7189次阅读
使用 GPUImage 实现一个简单相机 4年以前  |  5515次阅读
APP适配iOS11 5年以前  |  5487次阅读
 目录