iOS面试-数据库比较:SQLite vs. Core Data vs. Realm(未完待续)

SQLite

SQLite是使用最多的数据库引擎,并且是开源的。它实现了无配置,无服务要求的事务数据库引擎。SQLite可以在Mac OS-X, iOS, Android, Linux, 和 Windows上使用,可以被存储在跨平台磁盘文件的完善的数据库

SQLite的优点:

  • 独立于服务器
  • 零配置
  • 多进程和线程下安全访问。
  • 在表中使用含有特殊数据类型的一列或多列存储数据。

Core Data (推荐第三方:MagicalRecord)

Core Data 是App开发者可以使用的第二大主要的IOS存储技术。你需要根据数据类型和数据量进行管理和存储,SQLite和Core Data都有它们各自的优缺点。Core Data 更加关注于对象而不是传统的表数据库方法。使用Core Data,你可以存储一个Objective-C类的对象,(Core Data本身就是基于sqlite的封装,所以它的底层仍然是使用sqlite进行存储数据的)

尽管它们从本质上不相同,但是CoreData:

  • 比SQLite使用更多的内存、更多的存储空间。
  • 比SQLite在取数据方面更快,不需要书写大量的sql语句。
  • CoreData 的数据模型升级兼容性较差,如果模型不对,会导致程序连起都起不来。虽然提供了模型升级代码,但是在客户端的管理模型版本管理也会相对复杂
  • CoreData的好处是集成化数据动态加载icloud,包括上层的数据显示,都不需要重新进行数据装配,可以直接使用
  • CoreData,建立的表没有主键,添加时都要自己处理。它不是关系型数据库,处理多对多的关系时比较麻烦。
  • CoreData的一个比较大的痛点是多人合作开发的时候,管理CoreData的模型需要很小心,尤其是合并的时候,他的data model是XML格式的,手动resolve比较烦
  • CoreData还有其他sql所不具备的优点,比如对undo的支持多个context实现sketchbook类似的功能,为ManagedObject优化的row cash等。
  • CoreData是支持多线程,但需要thread confinement的方式实现,使用了多线程之后可以最大化的防止阻塞主线程
  • CoreData自定义升级麻烦,并且效率非常低下,升级的版本变动很难跟踪,有着各种限制(抛弃它主要原因),有些限制直接导致了没法继续开发下去

iOS 8之前,Core Data没有批处理,批量插入,删除,更新效率要比sqlite低很多(这也正是很多程序员因为Core Data批量更新数据效率之低而不得不放弃使用它的原因)。 iOS8之后的Core Data 更新,正好的解决了这个痛点: Batch Updates. Batch Updates在处理大量数据时速度明显提升.Batch Updates可用于批量快速更新数据,Asynchronous Fetching可用于异步抓取海量数据,并可以通过NSProgress实现进度跟踪和取消。


Batch Updates


在CoreData中想要更新大量数据,我们往往要将大量修改后的NSManagedObject加载到NSManagedObjectContext中并保存,这会占用大量内存,试想想在iPhone这样的内存有限的移动设备上将是个灾难,数据有可能丢失。你可能会采取批处理的方式,即一小批一小批的更新NSManagedObject并保存到NSManagedObjectContext中,但这样会花费很多时间,用户体验较差。

为了解决这个问题,苹果在NSManagedObjectContext加入了一个新的方法:executeRequest:error:,它接受一个NSPersistentStoreRequest类型的参数,返回类型为NSPersistentStoreResult

关于NSPersistentStoreRequest有些人可能比较熟悉,它是NSFetchRequest、NSSaveChangesRequest、NSBatchUpdateRequestNSAsynchronousFetchRequest的基类。后两个类是这次iOS8新加的

NSPersistentStoreResult是一个新加入的类,它也是一个基类,而且是抽象类,这个类作为executeRequest:error:返回内容的父类,相当于一个接口,它目前有两个子类:NSPersistentStoreAsynchronousResultNSBatchUpdateResult`。

NSBatchUpdateResult对应着前面的NSBatchUpdateRequestNSBatchUpdateRequest,它有点像NSFetchRequest:它允许你指定一个想要更新数据的实体;也可以指定一个affectedStores,它存储了一个接受更新请求的NSPersistentStore数组。(其实它是NSPersistentStoreRequest的属性);它也有一个谓词属性来做更新的条件,它跟NSFetchRequest中的谓词一样强大和灵活,类似于SQL的where语句;它允许你指定想要更新的字段,通过propertiesToUpdate属性来描述字段更新,它是一个字段,key为NSPropertyDescription或属性名字符串,value为NSExpression或常量。

NSBatchUpdateResult,它有一个result属性和resultType属性,result中的内容跟resultType有关,可能是成功或者失败,有可能是每行被更新的ID,也可能是被更新的行数。

需要注意的是,由于NSBatchUpdateRequest并不会先将数据存入内存,而是直接操作数据库,所以并不会引起NSManagedObjectContext的同步更新,所以你不仅需要获取NSBatchUpdateResult然后刷新NSManagedObjectContext对应的数据和UI界面,还需要保证更新后的数据满足数据库模型上的validation,因为NSManagedObjectContext没有感知Batch Updates,一些数据验证工作就落在了程序员的身上(你需要写一段代码验证更新后的数据是合法的,用户可不希望在跑步APP上看到自己今天跑步里程是个负数)。一旦有非法数据录入数据库,下次加载并修改NSManagedObject的时候就会导致数据验证失败。除了上面提到的这些,还要注意Batch Updates对数据库的操作是乐观锁,也就是假定很少会发生同时存取同一块数据的情况,所以你需要制定一个合理的”merge”策略来应付因同时更新数据产生的冲突。

Batch Updates的优势在于其效率,在处理上万条数据的时候,它执行的时间跟SQL语句执行时间相当。


Asynchronous Fetching


Asynchronous Fetching 的加入依然是为了解决CoreData读取海量数据所带来的问题。通过使用 Asynchronous Fetching,我们可以在抓取数据的同时不阻塞占用 NSManagedObjectContext,并可以随时取消抓取行为,随时跟踪抓取数据的进度。

设想我们平时用NSFetchRequest抓取数据的时候,我们会先用NSManagedObjectContextexecuteFetchRequest:error:方法传入一个NSFetchRequest,然后请求会被发送到NSPersistentStore,然后执行一段时间后返回一个数组,在NSManagedObjectContext更新后,这个数组被当做executeFetchRequest:error:的返回值返回到我们这里。

Asynchronous Fetching则不同,当我们将一个NSAsynchronousFetchRequest对象传入executeRequest:error:方法后会立即返回一个“未来的”NSAsynchronousFetchResultNSAsynchronousFetchRequest初始化时需要传入两个参数赋值给属性:

completionBlock属性,允许我们在抓取完成后执行回调block;
fetchRequest属性,类型是NSFetchRequest。也即是说虽然是异步抓取,其实我们用的还是以前的NSFetchRequest,当NSFetchRequest抓取结束后会更新NSManagedObjectContext,这也就意味着NSManagedObjectContext的并发类型只能是NSPrivateQueueConcurrencyTypeNSMainQueueConcurrencyType
于是当我们用NSAsynchronousFetchRequest抓取数据时,我们会先用NSManagedObjectContextexecuteRequest:error:方法传入一个NSAsynchronousFetchRequest,这个方法在NSManagedObjectContext上执行时,NSManagedObjectContext会立即制造并返回一个NSAsynchronousFetchResult,同时NSAsynchronousFetchRequest会被发送到NSPersistentStore。你现在可以继续编辑这个NSManagedObjectContext中的NSManagedObject,等到NSPersistentStore执行请求完毕时会将结果返回给NSAsynchronousFetchResultfinalResult属性,更新NSManagedObjectContext,执行NSAsynchronousFetchRequest的回调block。



Realm

Realm 是个新技术。Realm比前面提到的数据库解决方案更快,更高效。它是一个跨平台的移动数据库,不仅支持Android,iOS还支持macOS,Linux,ReactNative和Xamarin。

最棒的是你通过两行代码就可以处理所有的工作。Realm相比于SQLite和Core Data而言,更易于安装并且运行的更快。

如果你正在设计一款面向很多用户,有很多记录的程序,那么你从设计的一开始就需要特别注意它的可扩展性。Realm在这方面非常出色,并且能够让你快速的操作大量数据。

想要开始使用Realm,你所需要的仅仅是最低iOS 8或者OS X 10.9的系统。早期版本的系统并不支持本地存储与数据库的全新的简单解决方案。

Realm最主要的优势是:

  • 绝对免费
  • 快速,简单的使用
  • 没有使用限制(待定)
  • 为了速度和性能,运行在自己的持久化引擎上
  • 代码量少,几乎仅仅相当于sqlite的一半代码量

缺点:

  • 类名长度最大57个UTF8字符。
  • 属性名长度最大63个UTF8字符。
  • NSDataNSString属性不能保存超过16M数据,如果有大的可以分块。
  • 对字符串进行排序以及不区分大小写查询只支持"基础拉丁字符集"、"拉丁字符补充集"、"拉丁文扩展字符集 A" 以及"拉丁文扩展字符集 B"(UTF-8 的范围在 0~591 之间)。
  • 多线程访问时需要新建新的Realm对象。
  • Realm没有自增属性。也就是说对于我们习惯的自增主键,如果确实需要,我们要自己去赋值,如果只要求独一无二, 那么可以设为[[NSUUID UUID] UUIDString],如果还要求用来判断插入的顺序,那么可以用Date。
  • Realm支持以下的属性类型:BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData 以及 被特殊类型标记的NSNumber,注意,不支持集合类型,只有一个集合RLMArray,如果服务器传来的有数组,那么需要我们自己取数据进行转换存储。

读取性能测试


对比图
图片来源

推荐阅读更多精彩内容