iOS 数据持久化

iOS 数据持久化

文章目录

数据持久化

所谓的数据持久化,就是把数据保存到硬盘中,使得在应用程序重启之后可以继续访问之前保存的数据.

常用的数据持久化方式有以下几种:

  • 属性列表(Property List, plist)
  • 偏好设置(User Defaults)
  • 归档 (Archiving)
  • SQLite数据库
  • Core Data
  • 文件存储(File Storage)
  • Keychain

数据持久化的意义

  1. 数据保存:
    • 目的:将应用程序中的数据保存到非易失性存储,以便在应用程序关闭或重启后仍可以访问这些数据
    • 示例:保存用户输入的数据,应用设置,游戏进度
  2. 状态恢复:
    • 目的:在应用程序重新启动的时候恢复到之前的一个状态
    • 示例:保存应用的最后状态,以便用户可以从中断的地方继续
  3. 数据备份:
    • 定期备份数据,防止数据丢失
    • 定期备份数据库到云端
  4. 数据共享:
    • 允许多个应用程序或者设备之间共享数据
    • 使用云服务同步数据
  5. 离线访问:
    • 即使在网络不可用的情况下也能访问数据。
    • 离线模式下的地图应用、阅读应用等
  6. 历史记录:
    • 记录用户的操作记录,方便用户回顾之前的活动
    • 流览历史,购买记录
  7. 数据分析
    • 收集用户行为数据,用于分析和改进产品。
    • 收集用户使用频率、停留时间等数据。
  8. 用户个性化
    • 根据用户的偏好设置来定制内容和服务
    • 保存用户的喜好,推荐设置
  9. 安全性与合规性
    • 确保敏感数据的安全存储,并遵守相关法规要求。
    • 加密存储个人身份信息、健康数据等。
  10. 性能优化:
    • 通过缓存数据减少网络请求,提高应用程序的响应速度。
    • 缓存图片,APi的数据

数据存储的区域

数据存储的区域在内存和磁盘

内存缓存

对于使用频率比较高的数据,从网络或磁盘加载数据到内存以后,使用后并不马上销毁,下次使用直接从内存加载。

例如iOS中图片的加载,SDWebImage

磁盘缓存

将从网络加载的,用户操作产生的数据写入到磁盘,用户下次查看、继续操作时,直接从磁盘加载使用

例如搜索历史的缓存、用户输入内容草稿的缓存,SDWebImage

两者的区别

缓存分为内存缓存和磁盘缓存两种。

其中内存是指当前程序的运行空间,缓存速度快容量小,是临时存储文件用的,供CPU直接读取,比如说打开一个程序,他是在内存中存储,关闭程序后内存就又回到原来的空闲空间;

磁盘是程序的存储空间,缓存容量大、速度慢、可持久化。与内存不同的是磁盘是永久存储东西的,只要里面存放东西,不管运行不运行 ,他都占用空间!磁盘缓存是存在Library/Caches

NSCache

NSCache是iOS的一套缓存机制

属性
objc 复制代码
@property NSUInteger totalCostLimit;	// 能够缓存对象的最大数量,默认值是0,没有限制
@property NSUInteger countLimit;	// 设置缓存占用的内存大小
@property BOOL evictsObjectsWithDiscardedContent; //是否回收废弃内容,默认YES
方法
objc 复制代码
- (nullable ObjectType)objectForKey:(KeyType)key; //通过key获得缓存对象
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 缓存对象 设置成本数为0
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;//缓存对象,并指定key值对应的成本,用于计算缓存中所有对象的总成本。
- (void)removeObjectForKey:(KeyType)key; //删除指定对象。

- (void)removeAllObjects; //删除所有缓存对象。
代理
objc 复制代码
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;// 缓存对象即将被清理时调用,一般开发者用来调试,不能在此方法中修改缓存。
//在下列场景中会被调用:

//removeObjectForKey
//缓存对象超过NSCache的countLimit和otalCostLimit属性设置的限制
//App进入后台
//系统发出内存警告
//cache这个实例的生命周期结束前
和NSDictionary的区别

NSCache是线程安全的,不需要加线程锁

NSDictionary不是线程安全的

使用注意点
  1. 当收到内存警告的时候,而我们又调用removeAllObjects,则无法再继续往缓存中添加数据.
  2. 不提供缓存总的大小,想知道NSCache占用的内存大小,只有通过添加缓存的cost自己计算。
  3. NSCache自动释放内存的算法是不确定的,有时是按照LRU(最近最久未使用)释放,有时随机释放。
  4. NSCache中的数据在APP重启后会消失,因为NSCache只是将数据保存在内存 的。
objc 复制代码
NSCache* cache = [[NSCache alloc] init];
    cache.delegate = self;
    for (int i = 0; i < 10; i++) {
        [cache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost:i];
    }
    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [cache objectForKey:@(i)]);
    }
    NSLog(@"取出所有的NSCache================");
    
    [cache removeAllObjects];
    cache.totalCostLimit = 5;
    
    NSLog(@"设置缓存限制之后=====================");
    for (int i = 0; i < 10; i++) {
        [cache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost:i];
    }
    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [cache objectForKey:@(i)]);
    }
    NSLog(@"取出所有的NSCache================");
    [cache removeAllObjects];
    NSLog(@"设置缓存限制但是没有设置成本==========");
    for (int i = 0; i<10; i++) {
        [cache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i)];
    }
            
    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [cache objectForKey:@(i)]);
    }
    NSLog(@"取出所有的NSCache================");
            /// 清除缓存
    [cache removeAllObjects];

设置缓存限制且知道缓存成本数时,超出是会自动回收。但是设置缓存限制但不知道缓存成本数时不会自动回收。

回收时会调用 willEvictObject 的代理方法。

沙盒机制

iOS中的沙盒机制是一种安全机制.为了保证西哦桶那个的安全,iOS每一个app在安装的时候,都会创建属于自己的一个沙盒文件,应用程序只能访问自身的沙盒文件,不可以访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据的时候,的都需要经过权限认证,否则就无法获得到数据.所有的非代码文件都要保存在此,例如属性文件plist、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。

沙盒结构

注意每一次编译的代码都会生成一个新的沙盒路径,注意是编译而不是启动,所以模拟机和真机每次运行所得到的沙盒路径都是不一样的.线上版本的app沙盒真机不会生成新的沙盒路径.

这里在这个目录下分成四个文件夹:

  • Document:文档目录,要保存程序生成的数据,会自动分配到iCound中.保存应用运行是生成的需要的持久化的数据

  • Library:默认存放设置和其他状态信息,除了caches子目录之外其他目录都会被iclude同步。

    如果要想数据及时写入磁盘,还需要调用一个同步方法

    保存临时文件,"后续需要使用",例如:缓存图片,离线数据(地图数据)系统不会清理cache目录中的文件,这就要求程序开发的时候.自己提供cache目录的清理解决方式

    • Cache:存放体积大且不需要备份的数据

    • preference:保存应用所有偏好设置,iCloud会备份设置信息

    • SplashBoard:存储启动屏缓存,缓存文件格式为 ktx,本质上就是图片,如果启动屏不生效的问题可以考虑从删除该路径下相关缓存文件这个角度解决。

    • Application Support:此目录包含应用程序用来运行但应对用户隐藏的文件,如游戏的新关卡等文件。

    • Cooikes:系统会自动将App中网络请求的cookie保存为文件

  • SystemData:存放系统数据,无对外暴露的接口。

  • Tmp:临时文件,系统会自动清理.重新启动就会清理,保存应用运行的时候所需要的临时数据,使用完毕之后在从该目录删除.存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

获取沙盒路径的方式
访问沙盒目录常用C函数介绍
objc 复制代码
//文件路径搜索
FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

这里介绍一下三个参数:

  • 参数一 NSSearchPathDirectory directory:指定搜索的目录名称,比如这里用NSDocumentDirectory表明我们要搜索的是Documents目录。
  • 参数二 NSSearchPathDomainMask domainMask:搜索主目录的位置,NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。
  • 参数三 BOOL expandTilde:是否获取完整的路径,我们知道在iOS中的全写形式是/User/userName,该值为YES即表示写成全写形式,为NO就表示直接写成"~"。
objc 复制代码
		//获取沙盒根路径
    NSString *path = NSHomeDirectory();
    NSLog(@"沙盒根路径:%@", path);
    //Document路径
    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSLog(@"Document目录路径:%@", docDir);
    // 获取Library的目录路径
    NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
    NSLog(@"Libarary目录路径:%@", libDir);
    // 获取Caches目录路径
    NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    NSLog(@"Cacheas目录路径:%@", cachesDir);
    // library Preference
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSLog(@"偏好设置目录路径:%@", defaults);
    // 获取tmp目录路径
    NSString *tmpDir =  NSTemporaryDirectory();
    NSLog(@"tmp目录路径:%@", tmpDir);

持久化数据的存储方式

前面我们概述了这些持久化数据的存储方式,现在来比较详细的介绍一下这里的持久化数据的存储方式

XML属性列表

属性列表是一种XML格式的文件,拓展名为plist

如果对象是NSStringNSDictionaryNSArrayNSDataNSNumber等类型,就可以使用
writeToFile:atomically:方法直接将对象写到属性列表文件中

objc 复制代码
 NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString* filePath = [docPath stringByAppendingPathComponent:@"test.plist"]; // 创建一个test.plist文件
    NSLog(@"%@", filePath);
    NSDictionary* dict = @{@"name":@"krystal", @"age":@"18"};
    [dict writeToFile:filePath atomically:YES];
    NSDictionary* dicty = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSLog(@"%@", dicty);
    NSArray* array = @[@"krystal", @"18"];
    [array writeToFile:filePath atomically:YES];

    // 取数组
    NSArray* arrayA = [NSArray arrayWithContentsOfFile:filePath];
    NSLog(@"%@", arrayA);

输出结果:

我们也可以看到对应的文件被创建在了我们的Document下:

preference偏好设置

很多iOS应用都支持偏好设置,提供了一套标准的解决方案来为应用加入偏好设置功能,比如保存用户名,字体大小,密码,是否自动登录等。

每个应用都有个NSUserDefaults实例,可以通过它来存取偏好设置,不需要路径。其本身的创建类似于单例模式,我们在后面用不同的属性名再次申请创建,会覆盖之前的数据。

  • NSUserDefaults:简单数据快速读写,不能存储自定义类型。
  • UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法[defaults synchornize];强制写入。

优点:

  • 不需要关心文件名,系统会自动生成文件名
  • 快速做键值对
objc 复制代码
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSLog(@"%@", defaults);
    //存储数据
    [defaults setObject:@"nanxun" forKey:@"name"];
    [defaults setObject:@"123123hdasd" forKey:@"password"];

    // 同步调用,立刻写到文件中,不写这个方法会异步,有延迟
    [defaults synchronize];

    // 需要验证账号密码的地方,获取偏好设置对象
    NSUserDefaults *defaultsA = [NSUserDefaults standardUserDefaults];
    NSString *name = [defaultsA objectForKey:@"name"];
    NSString *password = [defaultsA objectForKey:@"password"];
    NSLog(@"name:%@ password:%@", name, password);
NSKeyedArchiver归档与解档

NSKeyedArchiver(归档):归档一般都是保存自定义对象的时候,使用归档。因为plist文件不能够保存自定义对象。如果一个字典中保存有自定义对象,如果把这个对象写入到文件当中,它是不会生成 plist文件的。如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复。

要想使用NSSecureCoding协议,我们就必须的遵守NSSecureCoding协议,NSSecureCoding协议也遵循了原来NSCoding这个协议,不过我们还需要遵循它的一个supportsSecureCoding方法,这样我们才能归档成功。

对应的一个实现方法:

objc 复制代码
- (void)encodeWithCoder:(NSCoder *)coder; // 解码
- (nullable instancetype)initWithCoder:(NSCoder *)coder; // 编码
+ (BOOL)supportsSecureCoding; // 是否支持

一定要完成上面的所有的代码才可以实现一个归档和解档

完整代码:

objc 复制代码
- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeInteger:self.age forKey:@"age"];
    [coder encodeObject:self.name forKey:@"name"];
    
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if (self) {
        _name = [coder decodeObjectForKey:@"name"];
        _age = [coder decodeIntForKey:@"age"];
    }
    return self;
}
+ (BOOL)supportsSecureCoding {
    return YES;
}
- (void)saveToFile {
    self.name = @"krystal";
    self.age = 15;
    NSError* error;
    NSString* docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSLog(@"%@", docu);
    NSString* path = [docu stringByAppendingString:@"/student.data"];
    NSData* data = [NSKeyedArchiver archivedDataWithRootObject:self requiringSecureCoding:YES error:&error];
    if (error) {
            NSLog(@"导入失败%@", error);
        }
    [data writeToFile:path atomically:YES];
}
- (void)readTheFile {
    NSError *error;
    NSString* docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString* path = [docu stringByAppendingString:@"/student.data"];
    NSData* unData = [NSData dataWithContentsOfFile:path];
    Student* student =  (Student*)[NSKeyedUnarchiver unarchivedObjectOfClass:[Student class] fromData:unData error:&error];
    NSLog(@"%@--%ld",student.name, student.age);
}

实现归档和解档一定要遵守NSSecureCoding协议,并且是实现他的一个归档解档方法,以及最后\+ (BOOL)supportsSecureCoding方法

数据库存储
  • SQLite:是目前主流的嵌入式关系型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。
  • CoreData:苹果基于sqlite封装的ORM(Object Relational Mapping)的数据库,直接对象映射.
  • FMDB:iOS端github使用最广的针对OC对sqlite的封装,支持队列操作
  • WCDB:微信技术团队开源的对sqlite操作的封装,支持对象和数据库映射,ORM数据库的一种实现,比FMDB更高效
什么是序列化和反序列化
  • 序列化:把对象转化为字节序列的过程
  • 反序列化:把字节序列恢复成对象
  • 作用:把对象写到文件或者数据库中,并且读取出来

在iOS中怎么实现一个序列化?

在iOS中就是采用我们的一个归档的方式来实现一个序列化的.在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档(解档)。