iOS 中的数据持久化

6,690 阅读6分钟

原文地址

在应用开发过程中,数据持久化是不可或缺的一部分。今天的文章会和大家介绍一下 iOS 中的数据持久化方案及相关特点,以便在开发过程中选择合适的数据持久化方案,避免出现不必要的错误。

沙盒机制

出于安全的原因,iOS 应用在安装时,为每个 App 分配了独立的目录,App 只能对自己的目录进行操作,这个目录就被称为沙盒。

与安卓系统不同,iOS 系统比较封闭,没有提供类似的内存卡扩展功能,也没有开放的文件管理,所以 iOS 系统的手机上是看不到文件目录的。

沙盒中主要包含4个目录: MyApp.appDocumentsLibraryTmp,目录结构如下:

截屏2022-05-19 16.55.08.png

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 数据库中的关系数据转换成对象模型。

截屏2022-05-19 21.46.42.png

加载数据模型
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更多技术好文