【译】IOS 数据存储:Core Data vs SQLite

原博客英文链接点此

编写一个简单的应用来测试两者的利弊

在IOS系统中,Core Data和SQLite是提供给开发者主要的两者数据存储方式。这两种技术分别都有自己的优点和缺点,到底使用那种方式取决与你要存储和管理数据的大小和类型。这篇文章和对应的简单工程对于Core Data 和 SQLite的使用可以有一个大体的了解,同时也可以比较两种方式的不同。一些基本的数据显示出来以供比较,数据包括内存的使用,应用CPU的使用情况,还有在iphone4s和5s数据占用磁盘的大小等。应用程序的屏幕截图如图1所示:

 图1

例子中的数据结构

要比较Core Data 和SQLite最好的办法就是存储相同的数据,尽管这不是一个完美的方法(因为单数据集合不可能完全展示Core Data 和SQLite存储数据的能力),总之,为了接近实际情况,这是一个合理的方法。我们例子中数据集合是一个列表,列表包括汽车、汽车制造商、车类型和车的详细信息。对于一个特定的车来说,我们需要存储汽车的类型,详细信息和生产商。表1展示数据结合的结构:

 表1

如果你不喜欢车,可以用冲浪板代替,如表2所示:

 表2

Core Data 简介

下面简单的介绍一下Core Data,当然,如果你想详细的学习Core Data你应该阅读苹果的Core Data Programming GuideDr. Dobb'scareful explanation

Core Data关注的是对象而不是传统的数据方法。当存储数据的时候,你实际上存储的是继承NSManagedObject的对象。一个具体的应用实际中会用到大量的对象在一起,形成一个对象图。就我们例子中的数据集合而言,一个汽车对象包括了车的类型,详细信息和制造商信息。你的应用可以直接修改对象;在存储这个对象的时候,NSManagedObjectContext类中用于保存的方法将会被调用。相反,你的应用也可以通过NSManagedObjectContext对象获取存储的对象。Core Data处理数据存储更新的所有操作。

图2显示了Core Data主要的结构

 图2

DataModal就是你定义数据对象和数据关系的地方。这一部分是通过Data Modal编辑器完成,此编译器包含在XCode IDE中。data modal文件以XML文件的形式保存在本机,然而,当你的应用编译的过程中,data modal 文件被编译成一个二进制的文件(后缀是.mond),这个二进制文件是要打包到你的ios应用中的。,每一个对象都被引用为一个”实体“,这个实体包含一个或多个属性。这里不要糊涂,一个实体就是一个对象,这个对象又是由属性组成。XCode 会为定义在Data Model 编辑器中的类生成源代码(.m和.h文件)。至此,选择data modal 然后在菜单栏选择Editor/Create NSManagedObject Subclass。

 图3

设计你的对象最重要的就是考虑清楚对象之间的关系。当一个对象包括另一个对象的引用的时候我们就可以根据”关系“来引用实例,这样的引用可以一对多吗?或者这样的引用可以多对多吗?在例子中,Car对象引用一个CarType,CarType对象也被Car的实例引用。CarType和Car对象的关系就是一对多的关系——一个CarType可以引用多个Car对象。这和SQL外键(foreign key)的概念非常相似,理解这个概念是非常重要的,因为对CarType对象的任何改变会影响到所有的Car 对象。图4展示了这个引用关系:

图4

无论存储使用SQLite数据库,二进制文件或者是iCloud,都是由持久性存储协同器(NSPersistentStoreCoordinator)处理实际物理存储的详细内容,你的应用不知道也不需要关心,它直接作用于应用的对象。使用Core Data
的优势之一就是可以任意地使用这些不同的存储类型。持久性存储协同器(Persistent Store Coordinator)可以处理不同的存储实例。你可能想使用iCloud来频繁的存储改变零碎的数据(比如汽油的价格),将持久的信息存储在本地(比如加油站的位置)。你最终如何存储,修改数据是由你的应用需求而定的。持久性存储协同器用编译好的数据模型文件来决定需要存储数据对象的结构和组织的方法。

管理对象上下文(NSManagedObjectContext)与持久性存储器获取,保存,和查询对象集合一同工作。有很多明显的特点,如果一个集合中的对象或对象的信息被修改,但应用不需要跟踪处理的时候。管理对象上下文也会扮演一个便签本的角色来记录对象的修改。如果你的应用对对象进行了修改知乎又需要恢复回原样,它是没问题的——应用可以通过使用未完成管理(NSUndoManager))或简单的重置管理对象上下文(使用重置的方法)忽略掉一部分对象的引用。

对于一个单个存储来说可能有不止一个对象上下文的实例。比如,一个应用不同的获取结果可能使用不同的上下文。结果,一个对象的实例就可以同时存在于两个上下文中,潜在地导致了数据的矛盾。在对象存储的时候,每一个管理对象都被分配一个独一无二的Id(如果一个对象从未被保存将会分配一个临时ID),当使用多个上下文的时候,应用也可以用这个ID来保证数据不变性。然而,如果应用对象的更改不确定是Core Data来实现的时候,应用就会强制跟踪对象的修改。简而言之,如果有强制因素最好使用多上下文(contexts)。

图5显示了管理对象上下文是如何管理core data 对象的

 图5

管理对象上下文是所有被操作对象的集合:因为所有对象一次存储所以不可能只保存一次NSManagedObject实例。如果你的应用必须同时有多个NSManagedObject工作,一定要留心内存的使用情况。在本文的例子中,当新建250,000个Car记录,内存的使用是260M。将内存的使用放到上下文中,iPhone5s有1G的内存,4s有512M的内存。如果是一台iPhone5s,如果创建超过350,000个记录,这个测试应用就会收到系统发来的内存警告(-(void)didReceiveMemoryWarning 函数被调用)。的确,260M已经是一个很大的数字,但别忘了250,000条对象都在内存中,这也就意味着所有的Car成员也都这内存中。如果你重启例子应用然后获取你刚刚创建的250,000条数据,你将会法相内存的使用是105M——远远要比用于新建数据时的260M小,发生了什么?问题的答案就是因为Core Data的一个重要的特性:对象断层(faulting)。

对象断层是在访问对象的时候管理对象上下文(Managed Object Contex)能够只加载内存中的一个对象。如果这个对象一直不可以访问,内存也就一直没有分配,因此减少了内存的使用。这一切对于使用者来说都是透明的:你的应用流畅的运行着。在本文的例子中,当访问Car对象的CarType,如果这个CarType没有在内存中;Core Data将自动加载它到内存为你显示正确的数据。NSManagedObject类的isFault方法可以让你的应用决定当访问对象的时候是将对象真正的加载到内存还是loaded into memory。

创建一个管理对象是有点难的。用alloc init模型来创建一个新的对象实例就要简单很多,应用应该调用NSEntityDescription insertNewObjectForEntityForName 方法,输入参数是Managed Object Context。下面的代码表示如何创建例子中的Car对象:


Car *car = [NSEntityDescription insertNewObjectForEntityForName:@"Car"

inManagedObjectContext:_managedObjectContext];

NSEntityDescription insertNewObjectForEntitryForName确实是一个很方便的方法。通过实体@Car新建一个NSManagedObject,然后插入到NSManagedObjectContext中。最重要的一点就是理解要Managed Object Context可以知晓所有的对象。

Core Data提供了很多的工具,而且可以处理绝大多数类型的数据。要完全掌握还有很多路要走。

ps:在本文的例子中,所有的Core Data数据位于源文件 GB_CarsCoreData.m and .h


SQLite

SQLite是一个开源,轻量级,功能强大,well-support,跨平台,self-contained被广泛使用的数据库。过去你可能在很多地方使用SQLite——就存储应用数据而言它确实是一个很好的选择。SQLite网站 是一个非常值得花时间浏览的,不想其它开源工程,SQLite文档和技术支持都是非常完善的。SQLite 3.7.13版支持IOS6和IOS7。SQLite同样支持Android 和windows phone,这是它的一大优点。如果你正在开发一个多平台的移动应用,如果没有用SQLite的话,你一定是疯了。

SQLite将数据存储在列表中,这个列表又包括一个或多个列,每列包含特定类型的数据。RDMS优势之一就是在处理常见数据集合方面。而不是每个列表包含数据所有的列,如果这样设计会导致需要复制每个表每行的数据,列表的设计结构,是通过引用其它已经保存复制数据的表(通过键)来实现。在我们的例子中,汽车制造商信息被保存在separate mfg_info中,这个表被car表所引用。

你的应用通过SQL语言和SQLite C API操作SQLite数据库。这意味着要将一条数据插入到SQLite中的同时也要在新插入的数据旁插入一个SQL声明。下面就是例子中插入声明的代码:

"insert into car (model, msrp, year, mfg_id, cartype_id,

cardetails_id) values ("Pinto", 17000, 1970, 1, 2, 3)"

很明显,要获得数据就必须要select一个SQL声明。比如,下面的代码就是实现通过select 声明从而获得所有汽车信息的:

"select * from car"

一个非常简单的情况就是要获得所有car列表信息;然而如果你想要获取特定一辆车的所有信息就会变得很麻烦。在本文的例子中,使用了多个声明,如下面所示:

"select car.model, car.year, car.msrp, cardetails.detailgroup, "

"cardetails.info_1, cardetails.info_2, manufacturer.hq_location, "

"manufacturer.name, manufacturer.num_employees, cartype.type, "

"cartype.type_desc from car inner join cartype on "

"car.cartype_id = cartype.id inner join cardetails on "

"car.cardetails_id = cardetails.id inner join manufacturer on "

"car.mfg_id = manufacturer.id where car.id = %@"

就像你看到的,即使一个简单的数据获取都需要你具有扎实的SQL知识。SQLite的另一个缺点就是保证所有的列和数据都处于正确的顺序是很麻烦的。比如,当我们将SELECT获取的结果排序的时候,如果你调用sqlite3_column_*()函数的时候搞错了列的索引,获取的数据也是错的。这种情况在我们后续添加删除新的列的时候会经常出现。

SQLite实现了C API;因此,你可以在与应用数据库交互的时候将NSStrings *和char *字符串相互转换。Objective C对SQLite做了一些封装——普遍使用的就是FMDB 。对于大多数应用来说,直接使用SQLite API 都不是过于繁琐。本文的例子就是直接调用SQLite API实现的。

Core Data 与SQLite的区别

Core Data 和 SQLite的本质不同,所以很难直接比较两个技术。但是,从一个开发者的角度,在一个应用中你可以权衡一下他们各自的优缺点,每个方法的代价和优势分别是什么?本文的例子是同时使用了两种技术。在主屏幕选择Core Data 或 SQLite方式,新建特定数目的数据。应用的内存用量每秒更新,storage size就是实际文件的大小。

通过这个测试工程,我们可以发现一些区别,试验机是一台iPhone 4s和一台iPhone5s,两台手机都是运行的ios 7.1.1的系统。手机的具体型号都写在了数据后的括号内。比如(4s)就表示测试数据是iPhone 4s手机上获得的,同理(5s)就表示测试数据是iPhone 5s手机上获得。

内存的使用情况

与SQLite相比,Core Data使用的内存要多40%到100%不等。考虑到Core Data设计(尤其NSManagedObjectContext是如何定位所有对象的)这样的结果也是合理的,Core Data的设计对象的内容是否有错误(意识到内存中的值是错误的)直接决定了每个对象是否需要使用一定的内存。

 表3:启动应用并获取所有Car的数据时内存的使用

这里记录了内存的使用情况。当新建数据的时候,所有的Car数据都会保存在内存中,较高的内存使用也反映了这一事实。但是实际使用中大多数用户并不会存储大量的数据。一个更好的测试办法就是应用刚刚启动获取所有Car数据的时候来进行测试比较。表3的数据就是这种情况。测试过程中为了获得这个结果,只能重启应用重新获得数据。

存储文件的大小

Core Data的方式需要更大的磁盘空间——大很多的空间——大约是SQLite的4倍,如表4所示:

 表4

存取速度

在获取数据的时候Core Data 和 SQLite都很快。在iPhone5s上测试的时候,差距十分微小。但在4s上,Core Data 要比 SQLite快两倍。表5的结果就是获取所有数据操作所花的时间。

 表5

结论

Core Data 和SQLite一些关键的不同点如下,请牢记:

SQLite:

SQLite就像介绍中所说的一样,它是轻量级的

SQLite对内存和磁盘空间的使用都相对较少

SQLite在代码实现麻烦而且易出错

SQLite在Android和windows phone同样适用


Core Data:

学习周期长:学好Core Data需要很长的时间

对象我们操作起来更容易

可以自动处理潜在的存储细节(支持iCloud)

具有撤销和重做(Undo and Redo)的特点


没有任何一种技术,框架或减肥药能让你的生活永远的美好。在设计一个应用的时候,有很多指标需要我们去考量。这篇文章只是在你设计下一个ios应用时关于存储选择的一些建议。我希望你下载文中例子程序 亲自测试一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容