iOS数据存储(上)

其实每位开发同学都会花很多时间去研究和整理技术相关的东西。但少要体系性地去写成文章,用于记录及沉淀,因为写文章其实并不容易,首先要收集和整理很多内容,耗费大部分业余时间,才能整理出一篇不错的文章。
我写这些内容,感触最大的是一种习惯的养成,以及技术地深入理解,俗话说“纸上得来终觉浅,觉知此事需躬行。”,很多时候我们只是了解到技术的表面,背后真正的原理却始终半知半解。而写一些技术文章,非常有助于深入、全面地理解技术的背后(虽然我仍然有许多内容还不理解,甚至理解得不够透彻,但只有大胆尝试,才有可能缩小这些不足)。另外一个就是培养个人的写作水平,不管怎么样,我们在工作中都会或多或少需要书写文档或者向上的汇报内容。通过书写博客文章,对这一块的帮助都是非常有帮助的。

下面还是回过头来聊一聊数据存储相关的讨论,其实数据存储是一个比较大的课题,其分为内存存储,磁盘存储,序列化、反序列化,存储方式,淘汰机制等。


NSUserDefaults

先看一些轻量级内容的存储吧,大家对这个类的使用应该习以为常了,其大致用法是这样的.

//存储数据
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];  
[userDefaults setObject:@"密码内容" forKey:@"userPassWord"]; 

//读取数据
NSString *passWord = [ user objectForKey:@"userPassWord"];

当然它还有很多其它的API,但我们发现其只支持一些基础数据类型,如 NSStringNSNumberNSDataNSURL,以及仅包含这些基础数据元素的NSArrayNSDictionary等数据结构。如果你要保存自定义的数据,当然就需要先把自定义数据序列化NSData,读取的时候再反序列化使用。这些后面会讨论一下序列化反序列化的问题。

但这里要注意的是,NSUserDefaults的另一个函数synchronize,其本意是直接把数据同步到磁盘,但有时候它会导致Crash!大部分情况是你在同步的过程中,其它数据源发生了变化,其本身是线程安全的,但其数据源的变化有时候却不是线程安全的。

这一切看起来都很完美,使用起来足够简单,存储简单,读取也简单。但是,这里还有一个但是...

NSUserDefaults在内部实现是这样的,其维护了一个plist文件,其会把数据插入到plist里面,整体读取和整体写入。这样就造成了一个问题,如果工程达到一定规模,其数据内容达到一定量的时候,其读取和写入就会耗费过多的I/O操作,就会出现时不时的卡顿。

于是网上有很多同学就自定义了NSUserDefaults,例如YTKKeyValueStore


YTKKeyValueStore

YTKKeyValueStore是唐巧开源的一个KV存储框架,其原理是利用Sqlite做存储媒介,存储的对象都是基于Key-Value的json数据,所以中间会有一个序列化和反序列化的过程。这与上面提到的NSUserDefaults的实现方式是不同的,可以有效避免性能问题。

但这个开源库也存在一些问题。

  • 缺少内存缓存
    少量的内存缓存可以明显加快一些常用的key-Value值的使用。

  • 过于臃肿的接口参数
    我们需要人为地指定数据存储的Path,另外一个居然还要指定数据库的Table,相比NSUserDefaults在接口上还不是很简洁。

另外网上还有其它人基于LevelDB写了一些其它的Key-Value存储框架,例如THLevelDB,这也是一个不错的思路,虽然LevelDBKey-Value的读取方面会比Sqlite要好,但在移动设备内,真正需要存储的value数目并不会太多,所以其能够提升的真实性能还是有限的,除非你的App需要大量存储这类信息。

其实手Q内部也有一个类似这样的存储框架,其接口与NSUserDefaults一样简洁,而且还有内存缓存,同时采用内部统一的数据存储读写接口,进行了线程安全的封装和保护,大大降低了阻塞主线程的可能。


NSCache

NSCache是苹果为iOS和Mac OS系统提供一个轻量级的Key-Value内存缓存框架,SDWebImageAFNetworking中的UIImageView+AFNetworking都使用这个策略,其与上面的NSUserDefaults不同的是,NSCache只是一个内存缓存,如果要永久缓存,还需要其它的手段。
这儿一篇文章专门介绍了NSCache的使用.里面有几个重要的思路为很多其它统一化的缓存框架提供了思路。

  • 限制缓存大小和缓存数目
@property NSUInteger countLimit;
@property NSUInteger totalCostLimit;
  • 一如NSUserDefaults一样简洁的接口
- (id)objectForKey:(id)key
- (void)setObject:(id)obj forKey:(id)key
- (void)removeObjectForKey:(id)key
- (void)removeAllObjects
  • 以及其在移除某个缓存时的Delegate回调
- (void)cache:(NSCache *)cache willEvictObject:(id)obj

以上特性,确实也给我们提供了一个有效、轻量的内存缓存、淘汰机制,所以我们在使用内存key-value缓存一些常用数据时,可以尽量避免使用NSDictionary,也可以省去很多淘汰的策略。


NSURLCache

说到NSCache,还另外一个是和NSURLRequest相关的缓存,名叫NSURLCache,记得我在2012年的时候曾经做过一个Web缓存,大致的需求就是要缓存之前看过的网页,于是就找到了NSURLCache,关于它有一个示例程序
NSURLCache是NSURLRequest所用到的缓存策略,如果我们要自定义一些内容的话,我们需要写一个类继承至它,并修改里面一些比较重要的方法。

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

并可以在初始化的时候设定好缓存大小和路径。

- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity
                          diskCapacity:(NSUInteger)diskCapacity
                              diskPath:(NSString *)path

并在程序启动的时候调用下面的方法

[NSURLCache setSharedURLCache:cache];

值得注意的是也可以利用- (NSString *)mimeTypeForPath:(NSString *)originalPath来设定存储的类型,例如只缓存图片,加快下次页面的展示。另外一个就是在取缓存的时候,要考虑到多线程的问题,做好数据保护。


序列化 & 反序列化

上面也谈到了,Key-Value缓存机制只能存储一些基础数据类型,而如果我们要存储一些自定义类型的数据,我们通常的做法是先将其序列化为NSData,然后再缓存起来。其实说白了,就是把内存中的某个对象,按照一定规则变成一个完整、连续的二进制流数据。而反序列化则刚好相反,是将二进制流数据变成实例对象。

所以其实我们也可以自己动手动写一些简单的序列化方法,只是要注意的是,准确地计算出不同数据所占用的字节数,然后把它们拼接到NSMutableData里面就可以了。

目前序列化用的比较多的就是NSKeyedArchiverNSKeyedUnarchiver,这两者需要类实现NSCoding协议。
此外还有NSJSONSerializationNSPropertyListSerialization,前者是基于一个Json对象进行操作,其可以直接将从Server端取到的Data数据直接转为NSDictionary或者NSArray。而后者则只是针对NSDictionary或者NSArray两者进行操作。

从整体性能上来看NSJSONSerialization优于NSPropertyListSerialization,而NSPropertyListSerialization 则优于NSKeyedArchiver。但后者能够对自定义数据进行序列化,所以其还是有优势的。

之前有位同学说,如果我们把自定义对象先转成json对象,再用NSJSONSerialization序列化,会不会比直接用NSKeyedArchiver性能更高呢,这里我没有做具体的测试,但我相信应该不会,因为其还涉及到对象的生成和转换,这里会多出来一些性能损耗。

自从有了NSJSONSerialization后,就可以抛弃JsonKit这些开源库了。

其实序列化是一个很耗CPU的事情,尤其是当我们的对象达到一定数量级,尤其是NSArray与NSDictionary的层层嵌套,所以请记得适当使用线程去处理这些事情,另外选择更加高效地序列化方法也是我们要考虑的。


总结

其实iOS上已经有不少缓存相关的组件,自动淘汰机制、内存缓存、文件分类,同时有缓存大小限制,淘汰时间机制,线程I/O操作,线程安全。把这些维度把统一纳入进来,通常就可以形成一个完备的缓存框架。
然而上面讲的主要是一些轻量级、简单的数据存储,后面还有关系型数据的存储与研究。

2015-10-24 16:31398
  • yz5459989912015-10-30 22:32

    还不错啊!

  • 6483746062015-10-30 22:38

    必须赞一个。

  • 🍁2015-10-30 22:39

    这个网站数度有点慢