在应用开发过程中,数据持久化是不可或缺的一部分。今天的文章会和大家介绍一下 iOS 中的数据持久化方案及相关特点,以便在开发过程中选择合适的数据持久化方案,避免出现不必要的错误。
沙盒机制
出于安全的原因,iOS 应用在安装时,为每个 App 分配了独立的目录,App 只能对自己的目录进行操作,这个目录就被称为沙盒。
与安卓系统不同,iOS 系统比较封闭,没有提供类似的内存卡扩展功能,也没有开放的文件管理,所以 iOS 系统的手机上是看不到文件目录的。
沙盒中主要包含4个目录: MyApp.app、Documents、Library、Tmp,目录结构如下:
MyApp.app
该目录用于存放应用本身的数据,包括资源文件和可执行文件。应用在被安装时,会将该目录签名,如果修改这个目录,签名会被改变,应用将无法启动。不会被 iTunes 和 iCloud 同步。
Documents
通常用来保存用户数据,用户数据通常包括希望向用户公开的任何文件(希望用户创建、导入、删除或编辑的任何内容)。该目录可以通过文件共享提供给用户。会被iTunes和iCloud同步。
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
Library
此目录通常包含应用程序运行时使用的文件,这些文件对用户是不可见的。该目录除 Caches 子目录以外会被 iTunes 和 iCloud 同步。
Library/Caches
通常用于存储运行时产生的临时文件及缓存文件,在空间不足时可能会被系统自动清除,因此,应用程序应该具备重新创建和下载这些文件的能力。
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
Library/Preferences
通常用于存储应用的偏好设置数据,不要试图直接访问该目录。修改此目录下的 plist 文件,可能会导致修改丢失、延迟反映更改或应用程序崩溃等意外情况。
Temp
用来保存不重要的临时文件,在应用程序没有运行时,系统会自动清除这些文件,因此,在应用程序终止后,不能依赖这些文件的持久性。不会被 iTunes 和 iCloud 同步。
NSString *tmpDir = NSTemporaryDirectory();
序列化与反序列化
要将对象存储到磁盘,需要将其转化成二进制数据,这一步叫做序列化。相反,将二进制数据转换成对象,则称为反序列化。
iOS 中的对象要实现序列化和反序列化,需要实现 NSCoding 协议:
-(void) encodeWithCoder:(NSCoder *)aCoder;
-(instancetype) initWithCoder:(NSCoder *)aDecoder;
举个例子:
@interface User : NSObject<NSCoding>
-(instancetype) initWithCoder:(NSCoder *)aDecoder {
_userName = [aDecoder decodeObjectForKey:@"usrName"];
_passWord = [aDecoder decodeObjectForKey:@"passWord"];
}
-(void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_userName forKey:@"userName"];
[aCoder encodeObject:_passWord forKey:@"passWord"];
}
@end
- 存储:
BOOL flag = [NSKeyedArchiver archiveRootObject:user toFile:path];
- 读取:
user = [NSKeyedUnarchiver unarchiveObjectWithFile: path];
此外,也可以使用一些优秀的三方库(如:YYModel)来实现对象的序列化和反序列化。
数据持久化方案介绍
NSUserDefaults
NSUserDefaults 是轻量级的数据持久化方案,主要用于存储应用程序的配置信息等一些比较小的数据。其特点如下:
- 是一个单例,且是线程安全的。
- 存储在 Library/Preferences 目录下。
- 以 plist 的形式进行存储。
- 当存储的数据是可变类型时,读取时会变为不可变。
synchronize
NSUserDefaults 会定时把缓存中的数据写入磁盘,而不是立即写入,为了防止在写完 NSUserDefaults 后程序退出导致的数据丢失,可以在写入数据后使用 synchronize 来强制立即将数据写入磁盘:
[[NSUserDefaults standardUserDefaults] synchronize];
但也要注意,不能频繁的使用 synchronize。
支持的数据类型
支持的数据类型有 NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary 等系统定义的数据类型,如果要存放其他数据类型或者自定义的对象,则必须将其转换成 NSData 存储。
SQLite3
用来存储大规模的数据,是一款轻型的关系型数据库。它具有操作简单、小巧、快速,可靠的特点。
创建和打开数据库
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [docPath stringByAppendingPathComponent:@"test.sqlite"];
const char *cFileName = fileName.UTF8String;
int result = sqlite3_open(cFileName, &_db); // 打开数据库文件, 如果数据库文件不存在,会自动创建数据库文件
if (result != SQLITE_OK) {
NSLog(@"打开数据库失败");
return;
}
NSLog(@"打开数据库成功");
建表
const char *sql = "CREATE TABLE IF NOT EXISTS test_table (id integer PRIMARY KEY AUTOINCREMENT, test_key char)";
char *errMsg = NULL;
result = sqlite3_exec(_db, sql, NULL, NULL, &errMsg);
if (result == SQLITE_OK) {
NSLog(@"创建表成功");
} else {
NSLog(@"创建表失败");
}
执行 sql 语句
sqlite3_stmt *stmt;
const char *insertSQL = "insert into test_table(test_key) values('test')";
int result = sqlite3_prepare_v2(_db, insertSQL, -1, &stmt, nil)
if (result == SQLITE_OK) {
NSLog(@"插入数据成功");
} else {
NSLog(@"插入数据失败");
}
常用的三方库
由于 sqlite3 的原生语言是 C 语言,与 OC 的使用风格不一样,对于 iOS 开发者来说,不是很友好,容易出错,这里列出一些封装好的三方库,大家可以根据实际情况选择使用。
- FMDB
- WCDB
- Realm
CoreData
CoreData 是苹果提供的一种应用数据管理框架,可以通过图形界面的方式快速定义 App 的数据模型,并且提供了对象模型和关系数据映射的能力,将模型对象转化成关系数据保存到 SQLite 数据库中,也可以将保存到 SQLite 数据库中的关系数据转换成对象模型。
加载数据模型
NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Student" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];
创建数据库
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingPathComponent:@"Student.sqlite"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
数据库关联缓存
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context.persistentStoreCoordinator = coordinator;
插入数据
Student * student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:context];
student.name = @"albert";
student.age = 22;
NSError *error = nil;
[context save:&error];
查询数据
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
NSPredicate *predicate = [NSPredicate predicateWithFormate:@"age=22"]; // 查询条件
request.predicate = predicate;
NSError *error = nil;
NSArray<Student *> *students = [context executeFetchRequest:request error:&error];
Keychain
提供了一种用于安全存储敏感信息方式。其特点如下:
- 保存到 keychain 中的信息不会因为卸载或重装 App 而丢失。
- keychain 是用 SQLite 进行存储的,苹果会对其进行加密。
- 适合存储一些比较小的数据。
- 可以通过 Group 的方式,在多个 App间共享。
API
- SecItemAdd:添加一个item
- SecItemUpdate:更新已存在的item
- SecItemCopyMatching:搜索一个已存在的item
- SecItemDelete:删除一个keychain item
三方库
系统提供的 API 不是 OC 风格,使用起来不是很友好,这里推荐两个三方库供大家选择:
- KeychainWrapper
- SAMKeychain
关注公众号 iOS学习社区 get更多技术好文