[IOS]理一下iOS本地持久化储存

以前以为APP端存储数据只有缓存,数据全部是由后端接口提供的。后来发现APP内置了类似SQLlite的数据库,这样每一个APP都可以作为一个独立的服务。其实游戏的一些分数肯定是写在硬盘里,不过是通过数据库的形式。

 

转:本地持久化数据存储,始终用的是GVUserDefaults这个对NSUserDefaults进行了扩展的第三方库。但随着业务的发展,需要存储的地方越来越多,GVUserDefaults也越来也不能适应需求,当我们都忍受不了的时候,经过一番商讨之后,决定使用FMDB这个封装了SQLite3的第三方库。此篇文章以此为主线,理一理数据库和本地化储存的一些知识,但是此篇文章丝毫没有提到CoreData,喜欢使用CoreData的同学,这里先说声对不起了浪费了您20s宝贵的时间。

此篇文章的逻辑如下图所示:

QQ截图20160322101753.png

图0-0 此篇文章的逻辑图

iOS本地持久化储存方式概述

说起iOS本地化储存的方式,大家估计在也熟悉不过了,NSUserDefault、File,Keychain、DataBase无非也就这几种方式。

  • NSUserDefault、File:这两种使用方式都很简单,需要注意的一点就是所存储的对象都需要遵守并实现NSCoding协议中的两个方法,适用的范围也都是一些小规模数据,其实NSUserDefault的底层实现还是以.plist文件进行储存的。
  • Keychain:用于储存一些私密信息,比如密码、证书等等,Keychain里保存的信息不会因App被删除而丢失,在用户重新安装App后依然有效。同样也适用于应用之间数据共享。
  • DataBase:说到储存,就不能不提DataBase技术,移动端所用的DBMS一般都是SQLite3,在iOS下,SQLite3的API是纯C语言的,这样我们一直以来面向对象开发的朋友们,突然找不到了对象,有点那么的惊慌失措。好在开源社区出现了像FMDB这样优秀的第三方框架帮我们找回了对象,同样苹果自己也出了新的技术就是所谓的CoreData(但CoreData并不是此篇文章介绍的重点)。数据库适合储存大规模数据,而且查找起来也比上述方式方便,所以大量储存的时候,还是要动用数据库,这也是我们放弃GVUserDefaults的原因。

 

你是用什么方法来持久保存数据的?这是在几乎每一次关于iOS技术的交流或讨论都会被提到的问题,而且大家对这个问题的热情持续高涨。本文主要从概念上把“数据存储”这个问题进行剖析,并且结合各自特点和适用场景给大家提供一个选择的思路,并不详细介绍某一种方式的技术细节。

谈到数据储存,首先要明确区分两个概念,数据结构和储存方式。所谓数据结构就是数据存在的形式。除了基本的NSDictionary、NSArray和NSSet这些对象,还有更复杂的如:关系模型、对象图和属性列表多种结构。而存储方式则简单的分为两种:内存与闪存。内存存储是临时的,运行时有效的,但效率高,而闪存则是一种持久化存储,但产生I/O消耗,效率相对低。把内存数据转移到闪存中进行持久化的操作称成为归档。

二者结合起来才是完整的数据存储方案,我们最常谈起的那些:SQLite、CoreData、NSUserDefaults等都是数据存储方案。当然在这些框架提供的方案之外,我们自己也可以按照个性化需求订制方案。这些存储方案侧重不同,支持的形式和方式也各不相同,在不同的使用场景下表现也是各有优劣。但万变不离其宗,无论什么方案都可以用下图来解释。

图1,存储方案示意图

以下将对四种存储方式进行详细的介绍:

  • NSUserDefaults,用于存储配置信息
  • SQLite,用于存储查询需求较多的数据
  • CoreData,用于规划应用中的对象
  • 使用基本对象类型定制的个性化缓存方案

NSUserDefaults存储配置信息

NSUserDefaults被设计用来存储设备和应用的配置信息,它通过一个工厂方法返回默认的、也是最常用到的实例对象。这个对象中储存了系统中用户的配置信息,开发者可以通过这个实例对象对这些已有的信息进行修改,也可以按照自己的需求创建新的配置项。

图2,笔者手机中[NSUserDefaults standardUserDefaults]内容

NSUserDefaults把配置信息以字典的形式组织起来,支持字典的项包括:字符串或者是数组,除此之外还支持数字等基本格式。一句话概括就是:基础类型的小数据的字典。操作方法几乎与NSDictionary的操作方法无异,另外还可以通过指定返回类型的方法获取到指定类型的返回值。

图3,NSUserDefaults提供的指定返回类型的方法列表

NSUserDefaults的所有数据都放在内存里,因此操作速度很快,并还提供一个归档方法:+ (void)synchronize。开发者自定义的配置项(如图2中的最后一项 key:alkdjfkladsjfmm)会以plist格式的文件归档在相应应用目录的/Library/Preferences/[App_Bundle_Identifier].plist文件。再次初始化获得实例对象后,框架会把用户自定义的这个配置和系统配置合并得到完整数据。

SQLite存储查询需求较多的数据

iOS的SDK里预置了SQLite的库,开发者可以自建SQLite数据库。SQLite每次写入数据都会产生IO消耗,把数据归档到相应的文件。

SQLite擅长处理的数据类型其实与NSUserDefaults差不多,也是基础类型的小数据,只是从组织形式上不同。开发者可以以关系型数据库的方式组织数据,使用SQL DML来管理数据。 一般来说应用中的格式化的文本类数据可以存放在数据库中,尤其是类似聊天记录、Timeline等这些具有条件查询和排序需求的数据。

每一个数据库的句柄都会在内存中都会被分配一段缓存,用于提高查询效率。另一个方面,由于查询缓存,当产生大量句柄或数据量较大时,会出现缓存过大,造成内存浪费。

SQLite的使用起来要比NSUserDefaults复杂的多,因此建议开发者使用SQLite要搭配一个操作控件使用,可以简化操作。笔者开发的SQLight是一款对SQLite操作的封装,把相对复杂的SQLite命令封装成对象和方法,可以供大家参考。大家可以在Github上获取这个工程的代码进一步了解。

CoreData规划应用中对象

官方给出的定义是,一个支持持久化的,对象图和生命周期的自动化管理方案。严格意义上说CoreData是一个管理方案,他的持久化可以通过SQLite、XML或二进制文件储存。如官方定义所说,CoreData的作用远远不止储存数据这么简单,它可以把整个应用中的对象建模并进行自动化的管理。

图4,官方文档中解释CoreData给出的对象图示例

正如上图所示,MyDocument是一个对象实例,有两个Collection:Employee和Department,存放各自的对象列表。MyDocument、Employee和Department三个对象以及他们之间的关系都通过CoreData建模,并可以通过save方法进行持久化。

从归档文件还原模型时CoreData并不是一次性把整个模型中的所有数据都载入内存,而是根据运行时状态,把被调用到的对象实例载入内存。框架会自动控制这个过程,从而达到控制内存消耗,避免浪费。

无论从设计原理还是使用方法上看,CoreData都比较复杂。因此,如果仅仅是考虑缓存数据这个需求,CoreData绝对不是一个优选方案。CoreData的使用场景在于:整个应用使用CoreData规划,把应用内的数据通过CoreData建模,完全基于CoreData架构应用。

苹果官方给出的一个示例代码,结构相对简单,可以帮助大家入门CoreData。

使用基本对象类型定制的个性化缓存方案

之前提到的NSUserDefaults和SQLite适合存储基础类型的小数据,而CoreData则不适合存储单一的数据,那么对于类似图片这种较大的数据要用什么方式储存呢?我给出的建议就是:自己实现一套存储方案。说到订制存储方案大家非常容易质疑,这是不是又在重新发明轮子。我可以非常明确的告诉大家,这绝不是在重新发明轮子。首先要明确,这个所谓的定制方案适用于互联网应用中对远程数据的缓存,几个限制条件缺一不可。

从需求出发分析缓存数据有哪些要求:按Key查找,快速读取,写入不影响正常操作,不浪费内存,支持归档。这些都是基本需求,那么再进一步或许还需要固定缓存项数量,支持队列缓存,缓存过期等。从这些需求入手设计一个缓存方案并不十分复杂,Kache是笔者根据开发应用的需求开发的一套缓存组件,通过分析Kache希望可以给大家一个思路。

图5,Kache架构图

如上图所示,Kache扮演的是一个典型缓存角色。应用加载远程数据生成应用数据对象的同时,通过Kache把数据缓存起来,再次请求则直接通过Kache获取数据。

缓存对象可以是NSDictionary、NSArray、NSSet或NSData这些可直接归档的类型,每个缓存对象对应一个Key。缓存对象包括数据和过期时间,内存中存放在一个单例字典中,闪存中每个对象存为一个文件。Key空间按照各种顺序存放缓存对象的Key集合,Pool为固定大小的数组,当数量达到上限,最早过期的一个Key将被删除,对应的缓存对象也被清除。Queue也是固定大小的数组,以先进先出的规则管理Key的增删。 每一次有新的缓存对象存入,自动检测Key空间中过期的集合并清除。

此外,控件提供save和load方法支持持久化和重新载入。

Kache最初设计为存放图片缓存,之后也曾用于缓存文本数据,由于使用了过期和归档相结合的逻辑,可以保证大部分命中的缓存对象都在内存中,从而获取了较高的效率。读者可以从Github上获取Kache源码了解更多。

以上介绍了几种iOS开发中经常会遇到的储存数据方法,从其存储原理、使用方式和适用场景几方面进行进了简单的对比。事实上每一款应用都很难采用一种单一的方案完成整个应用的数据储存任务,需要根据不同的数据类型,选择最合适的方案,以便整个应用获得良好的运行时性能。

[转]大公司里怎样开发和部署前端代码?

作者:张云龙
链接:http://www.zhihu.com/question/20790576/answer/32602154
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

接下来,我想从原理展开讲述,多图,较长,希望能有耐心看完。

—————————- 我是一条分割线 —————————-

让我们返璞归真,从原始的前端开发讲起。上图是一个“可爱”的index.html页面和它的样式文件a.css,用文本编辑器写代码,无需编译,本地预览,确认OK,丢到服务器,等待用户访问。前端就是这么简单,好好玩啊,门槛好低啊,分分钟学会有木有!

然后我们访问页面,看到效果,再查看一下网络请求,200!不错,太™完美了!那么,研发完成。。。。了么?

等等,这还没完呢!对于大公司来说,那些变态的访问量和性能指标,将会让前端一点也不“好玩”。

看看那个a.css的请求吧,如果每次用户访问页面都要加载,是不是很影响性能,很浪费带宽啊,我们希望最好这样:


利用304,让浏览器使用本地缓存。但,这样也就够了吗?不成!304叫协商缓存,这玩意还是要和服务器通信一次,我们的优化级别是变态级,所以必须彻底灭掉这个请求,变成这样:


强制浏览器使用本地缓存(cache-control/expires),不要和服务器通信。好了,请求方面的优化已经达到变态级别,那问题来了:你都不让浏览器发资源请求了,这缓存咋更新?

很好,相信有人想到了办法:通过更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。好像这样:


下次上线,把链接地址改成新的版本,就更新资源了不是。OK,问题解决了么?!当然没有!大公司的变态又来了,思考这种情况:


页面引用了3个css,而某次上线只改了其中的a.css,如果所有链接都更新版本,就会导致b.css,c.css的缓存也失效,那岂不是又有浪费了?!

重新开启变态模式,我们不难发现,要解决这种问题,必须让url的修改与文件内容关联,也就是说,只有文件内容变化,才会导致相应url的变更,从而实现文件级别的精确缓存控制。

什么东西与文件内容相关呢?我们会很自然的联想到利用 数据摘要要算法 对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。好了,我们把url改成带摘要信息的:


这回再有文件修改,就只更新那个文件对应的url了,想到这里貌似很完美了。你觉得这就够了么?大公司告诉你:图样图森破!

唉~~~~,让我喘口气

现代互联网企业,为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到CDN节点上,网页中引用的资源也会变成对应的部署路径:


好了,当我要更新静态资源的时候,同时也会更新html中的引用吧,就好像这样:


这次发布,同时改了页面结构和样式,也更新了静态资源对应的url地址,现在要发布代码上线,亲爱的前端研发同学,你来告诉我,咱们是先上线页面,还是先上线静态资源?

  1. 先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本的资源当做新版本缓存起来,其结果就是:用户访问到了一个样式错乱的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。
  2. 先部署资源,再部署页面:在部署时间间隔之内,有旧版本资源本地缓存的用户访问网站,由于请求的页面是旧版本的,资源引用没有改变,浏览器将直接使用本地缓存,这种情况下页面展现正常;但没有本地缓存或者缓存过期的用户访问网站,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误,但当页面完成部署,这部分用户再次访问页面又会恢复正常了。

好的,上面一坨分析想说的就是:先部署谁都不成!都会导致部署过程中发生页面错乱的问题。所以,访问量不大的项目,可以让研发同学苦逼一把,等到半夜偷偷上线,先上静态资源,再部署页面,看起来问题少一些。

但是,大公司超变态,没有这样的“绝对低峰期”,只有“相对低峰期”。So,为了稳定的服务,还得继续追求极致啊!

这个奇葩问题,起源于资源的 覆盖式发布,用 待发布资源 覆盖 已发布资源,就有这种问题。解决它也好办,就是实现 非覆盖式发布


看上图,用文件的摘要信息来对资源文件进行重命名,把摘要信息放到资源文件发布路径中,这样,内容有修改的资源就变成了一个新的文件发布到线上,不会覆盖已有的资源文件。上线过程中,先全量部署静态资源,再灰度部署页面,整个问题就比较完美的解决了。

所以,大公司的静态资源优化方案,基本上要实现这么几个东西:

  1. 配置超长时间的本地缓存 —— 节省带宽,提高性能
  2. 采用内容摘要作为缓存更新依据 —— 精确的缓存控制
  3. 静态资源CDN部署 —— 优化网络请求
  4. 更资源发布路径实现非覆盖式发布 —— 平滑升级

全套做下来,就是相对比较完整的静态资源缓存控制方案了,而且,还要注意的是,静态资源的缓存控制要求在前端所有静态资源加载的位置都要做这样的处理。是的,所有!什么js、css自不必说,还要包括js、css文件中引用的资源路径,由于涉及到摘要信息,引用资源的摘要信息也会引起引用文件本身的内容改变,从而形成级联的摘要变化,大概示意图就是:


好了,目前我们快速的学习了一下前端工程中关于静态资源缓存要面临的优化和部署问题,新的问题又来了:这™让工程师怎么写码啊!!!

要解释优化与工程的结合处理思路,又会扯出一堆有关模块化开发、资源加载、请求合并、前端框架等等的工程问题,以上只是开了个头,解决方案才是精髓,但要说的太多太多,有空再慢慢展开吧。或者大家可以去我的blog看其中的一些拆解:fouber/blog · GitHub

总之,前端性能优化绝逼是一个工程问题!

以上不是我YY的,可以观察 百度 或者 facebook 的页面以及静态资源源代码,查看它们的资源引用路径处理,以及网络请中静态资源的缓存控制部分。再次赞叹facebook的前端工程建设水平,跪舔了。

建议前端工程师多多关注前端工程领域,也许有人会觉得自己的产品很小,不用这么变态,但很有可能说不定某天你就需要做出这样的改变了。而且,如果我们能把事情做得更极致,为什么不去做呢?

另外,也不要觉得这些是运维或者后端工程师要解决的问题。如果由其他角色来解决,大家总是把自己不关心的问题丢给别人,那么前端工程师的开发过程将受到极大的限制,这种情况甚至在某些大公司都不少见!