文章目录
  1. 1. 前言
  2. 2. 应用沙盒
  3. 3. 存储方式
  4. 4. NSUserDefaults
  5. 5. plist属性列表(XML)
  6. 6. NSKeyedArchiver归档 (NSCoding)
  7. 7. SQLite3
  8. 8. Core Data (不提倡使用)
  9. 9. 第三方框架
  10. 10. 参考

前言

本文对iOS中数据存储方式做一总结,涉及到应用程序沙盒(Sandbox)和5种常用的数据存储方式,最后介绍了主流的第三方框架FMDB。

应用沙盒

  1. 每个iOS应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他文件系统隔离。应用必须待在自己的沙盒里,其他应用不能访问该沙盒。
  2. 应用沙盒的文件系统目录,如下图所示:
    Sandbox文件目录
    (关于Simulator里的app沙盒文件路径位置,详见iOS开发中Simulator相关路径汇总
  3. 应用沙盒结构分析

    1. Documents: iTunes同步设备时回备份该目录。 用于保存app运行时生成的需要持久化的数据,如游戏的存档信息。
    2. Library/Caches: iTunes不会备份。用于保存体积大、且不需要备份的非重要数据。
    3. Library/Preferences: iTunes会备份。用于保存应用的所有偏好设置,iOS的Settings选项会在该目录中查找应用的设置信息。
    4. tmp: iTunes不会备份。用于保存应用运行时所需要的临时数据,使用完毕后应将信息从该目录中删除掉。应用没有运行时,系统也可能会清除该目录下的文件。
  4. 目录代码访问方式

    • 沙盒根目录
      NSString* home = NSHomeDirectory();
    • Documents

      有2种方式:

      • 由根目录拼接”Documents”字符串:NSString* documents = [home stringByAppendingPathComponent:@"Documents"];
      • 使用NSSearchPathForDirectoriesInDomains函数

        1
        2
        3
        4
        // NSUserDomainMask表示从用户文件夹下找,YES表示展开路径中的波浪字符~(即展开绝对路径)
        NSArray* array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
        // 只有一个目录符合,所以可以直接取0
        NSString* documents = [array objectAtIndex:0];
    • Library/Caches
      与Documents类似,不过方式2中用 NSCachesDirectory 替换 NSDocumentDirectory

    • Library/Preferences
      使用 NSUserDefaults类存取该目录下的设置信息。详见后文。

    • tmp
      与根目录类似,使用NSString* tmp = NSTemporaryDirectory()即可。

存储方式

iOS提供了5种常用的数据存储方式:

文件类:

  1. NSUserDefaults 来设置偏好(Preference)
  2. plist属性列表(XML)
  3. NSKeyedArchiver归档 (NSCoding)

数据库类:(虽然最终也是.db等文件形式存储,但并不是直接操作文件)

  1. SQLite3
  2. Core Data

依次从轻量到重量、由简单到复杂,适用于不同的场景。

NSUserDefaults

NSUserDefaults 是一个层次化的进程间的key-value持久化仓库,被优化来存储用户的设置。 NSUserDefaults is intended for relatively small amounts of data, queried very frequently, and modified occasionally. Using it in other ways may be slow or use more memory than solutions more suited to those uses.

  • 基本用法:
1
2
3
4
5
6
7
// 获得标准的userDefaults
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
// 存数据
[defaults setObject:@"kkk" forKey:@"username"];
...
// 取数据
NSString* username = [defaults objectForKey:@"username"];

记录的数据被放在了/Libraray/Preferences/的folder里,是一个以 app identifier 命名的.plist文件,如com.XXX.mobile.APPName.plist

有的文章说存完数据后,还需要再调用[defaults synchronize];,使数据被立刻存入文件,防止因为程序意外退出等造成的数据丢失。Apple给出了新的说法:synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. 该方法在新版本中将被废弃,因此不需要调用这个同步方法,放心set就够了。

  • 高级用法

重点参考: http://blog.csdn.net/omegayy/article/details/7480567http://mt.sohu.com/20161116/n473384662.shtml 这两篇文章。
NSUserDefaults 默认包含5个Domain,并按照这些Domain的优先顺序(由上到下),依次从search list中搜索要查找的key:

Domain State 说明
NSArgumentDomain volatile (变化的,每次启动app都需要设置) 它由命令行参数组成(如果程序为命令行启动),使用 NSArgumentDomain 常量标识,系统自动把命令行参数放到这个域中。
Application (Identified by the app’s identifier) persistent 数据文件名为.plist。使用standardUserDefaults setObject: 所保存的数据就放在该文件。
NSGlobalDomain persistent 包含对所有APP有效的设置项,通过NSGlobalDomain 常量标识。这个域是系统Framework用来存储整个系统适用的设置项值,不应该被APP来存储特定APP的值。如果你想修改GlobalDomain中的设置项,那么应该在applicationDomain中加入同名的设置项,来设置值。
Languages Domain volatile 对于AppleLanguages 设置项中的每一种语言,系统把语言相关的(language-specific)设置项值存入到基于这个语言名字命名的特定域中。很多Foundation中的类(比如:NSDate、NSDateFormatter等等)使用特定语言域中的信息修改它们的行为。
NSRegistrationDomain volatile 可以通过例如 NSUserDefaults.standardUserDefaults().registerDefaults([“maxCount”: 3]) 在代码中进行的最低优先级的设置
  • 应用1,在NSArgumentDomain中设置语言,可以不切换iPad/iPhone的语言来测试app的国际化:
    在App的 Arguments Passed On Launch里,添加 -AppleLanguages(zh-Hans) 启动参数,那么app使用的就是Localizable.strings(Chinese (simplified))的文件中的信息了。
  • 应用2,使用NSUserDefaults.standardUserDefaults().registerDefaults(["maxCount": 3])来设置maxCount的默认值,如果其他domain都没有设置的话,这里最后一道防线能防止该值为0.

plist属性列表(XML)

是一种XML格式的文件,如果对象是NSString, NSDictionary,NSData, NSNumber等类型,就可以使用NSDictionary的writeToFile:atomically:方法直接将对象写到一个plist文件中

1
2
3
4
5
6
7
// 将数据封装成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"母鸡" forKey:@"name"];
[dict setObject:@"15013141314" forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 将字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];

读取

1
2
3
// 读取Documents/stu.plist的内容,实例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@", [dict objectForKey:@"name"]);

之前介绍的 NSUserDefaults在application域做的设置也是保存在了.plist文件中。

NSKeyedArchiver归档 (NSCoding)

如果对象是NSString, NSDictionary,NSData, NSNumber等类型,并且自定义的类实现了NSCoding协议,可以使用[NSKeyedArchiver archiveRootObject:]进行归档(存储)到指定文件路径,使用[NSKeyedUnarchiver unarchiveObjectWithFile:]来恢复(读取)对象。

这里介绍一个处理JSON简单高效的第三方库MJExtension,引入该第三方库后,只需要在 自定义的类 .m文件中 加入头文件#import “MJExtension.h”, 在@implementaion 中添加宏定义 MJCodingImplementation, 即可以自动完成 NSCoding的实现,非常方便。(该库利用了Ojbective-C的runtime特性来动态加载成员变量,实现了JSON <-> Object, Core Data 等数据间的自动转换, 值得使用)

SQLite3

SQLite3是一款开源的嵌入式关系型数据库,基于c语言。可移植性好、易使用、内存开销小。
SQLite3常用的5种数据类型:text、integer、float、boolean、blob
在iOS中使用SQLite3,首先要添加库文件libsqlite3.tbd ; 然后使用sqlite3_open, sqlite3_exec, sqlite3_prepare_v2, sqlite3_finalize, sqlite3_step,等方法创建、更新数据库和表,详见 SQLite3使用

由于SQLite3是直接使用类SQL语句的,因此使用不是很方便。文章使用iOS原生sqlite3框架对sqlite数据库进行操作 对SQL的增、删、改、查进行了函数层上的封装,函数参数基本为 table名,列名-值的dictionary,以及回调block, 本质上仍是组装SQL语句,不过提高了便捷性。

Core Data (不提倡使用)

Core Data是对SQLite3的面向对象的封装,好处:能够合理管理内存,避免使用sql的麻烦, 更高效. 不需要我们手动去创建model类, 可以通过一些操作, 制动生成.

操作过程通常分为以下几步:
创建管理上下可以细分为:加载模型文件->指定数据存储路径->创建对应数据类型的存储->创建管理对象上下方并指定存储。经过这几个步骤之后可以得到管理对象上下文NSManagedObjectContext,以后所有的数据操作都由此对象负责。

工作原理:

1
2
3
4
5
6
7
NSManagedObjectContext 临时数据库 向NSPersistentStoreCoordinator 持久化存储助理 发送一个key(model名字)
NSPersistentStoreCoordinator 通过这个key 在 NSManagedObjectModel 数据模型中 找到这个model 对应的 表
NSManagedObjectModel 将这个表名 返回给 NSPersistentStoreCoordinator
NSPersistentStoreCoordinator 通过 表名 找到 给表的 file 路径
NSPersistentStoreCoordinator 将这个路径 返回给 NSManagedObjectContext
NSManagedObjectContext 对 数据进行 处理(增, 删 , 该, 查)
NSManagedObjectContext 保存数据 save

重点参考 objccn 里 CoreData 一章。

缺点: 性能(速度和内存消耗)。假设更新10000个条目的一个字段,使用core data需载入10000个条目对象,10000次数据库操作分别修改;而使用SQL只需一条语句,一次数据库操作。 拿查阅来说,Core Data 的性能是 FMDB 的六分之一不到,Realm 的十分之一不到。
参看 唐巧的 我为什么不喜欢 Core Data [易崩溃;难学习;性能差]

第三方框架

  • FMDB
    使用Objective-C对SQLite3进行封装的第三方库,提供多线程支持。本质上仍使用SQL语句进行表操作。详见我专门整理的一篇FMDB使用札记

  • YTKKeyValueStore
    封装提供了key-value式的条目存储方式,结果通过SQLite3自动保存到数据库。适用于LevelDB(NoSQL, 非关系型数据模型)概念的客户端数据存储。详见YTKKeyValueStore

一个使用例子:

1
2
3
4
5
6
7
8
9
10
YTKKeyValueStore *store = [[YTKKeyValueStore alloc] initDBWithName:@"test.db"];
NSString *tableName = @"user_table";
[store createTableWithName:tableName];
// 保存
NSString *key = @"1";
NSDictionary *user = @{@"id": @1, @"name": @"tangqiao", @"age": @30};
[store putObject:user withId:key intoTable:tableName];
// 查询
NSDictionary *queryUser = [store getObjectById:key fromTable:tableName];
NSLog(@"query data result: %@", queryUser);

有关LevelDB, 参考 https://www.zhihu.com/question/38933764/answer/80312254

参考

文章目录
  1. 1. 前言
  2. 2. 应用沙盒
  3. 3. 存储方式
  4. 4. NSUserDefaults
  5. 5. plist属性列表(XML)
  6. 6. NSKeyedArchiver归档 (NSCoding)
  7. 7. SQLite3
  8. 8. Core Data (不提倡使用)
  9. 9. 第三方框架
  10. 10. 参考