当前位置: 首页 > news >正文

「iOS」————持久化


IOS——持久化

为什么持久化?

数据保存

  • 目的:将应用程序中的数据保存到非易失性存储中,以便在应用程序关闭或重启后仍能访问这些数据。

  • 示例:保存用户输入的数据、应用设置、游戏进度等。

状态恢复:

  • 目的:在应用程序重新启动时恢复到之前的状态。
  • 示例:保存应用的最后状态,以便用户可以从中断的地方继续。

数据备份

  • 目的:定期备份数据,以防数据丢失。
  • 示例:定期备份数据库到云端或本地存储。

数据共享:

  • 目的:允许多个应用程序或设备之间共享数据。
  • 示例:使用云服务同步数据,如 iCloud、Dropbox 等。

离线访问:

  • 目的:即使在网络不可用的情况下也能访问数据。
  • 示例:离线模式下的地图应用、阅读应用等。

历史记录:

  • 目的:记录用户的操作历史,以便用户可以回顾之前的活动。
  • 示例:浏览历史、购买记录等。

数据分析:

  • 目的:收集用户行为数据,用于分析和改进产品。
  • 示例:收集用户使用频率、停留时间等数据。

用户个性化:

  • 目的:根据用户的偏好定制内容和服务。
  • 示例:保存用户喜好、推荐设置等。

安全性与合规性:

  • 目的:确保敏感数据的安全存储,并遵守相关法规要求。
  • 示例:加密存储个人身份信息、健康数据等。

性能优化:

  • 目的:通过缓存数据减少网络请求,提高应用程序的响应速度。
  • 示例:缓存图片、API 数据等。

数据持久化方式

  • NSUserDefault:简单数据快速读写。

  • Property list: 属性列表的文件存储

  • Archiver:归档。

  • SQLite:本地数据库。

  • CoreData:CoreData 是基于 sqlite 的封装。

数据存储区域

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

内存缓存

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

例如:iOS的图片加载

磁盘缓存

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

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

对比项内存缓存磁盘缓存
速度极快较慢
容量小(受内存限制)大(受磁盘限制)
持久性易失(进程结束即丢失)持久(重启后仍在)
适用场景热点、临时、频繁访问数据历史、草稿、长期数据
实现难度简单

因此实际开发中,通常采取二级缓存的方法:

  • 先查内存缓存,内存没有再查磁盘缓存,磁盘没有再查网络或重新生成。

沙盒机制

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

应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此。

例如属性文件plist、文本文件、图像、图标、媒体资源扽等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。

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

截屏2022-05-19 16.55.08.png

  • MyApp.app:包含了所有的资源文件和和可执行文件,上架前经过数字签名,上架后不可修改。

  • Documents:文档目录,要保存程序生成的数据,会自动被分到iCloud中。保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录。(注意点:不要保存从网络上下载的文件,否则会无法上架!)

  • Library

    • 用户偏好,使用 NSUserDefault 直接读写!

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

    • 保存临时文件,“后续需要使用”,例如:缓存图片,离线数据(地图数据

    • 系统不会清理 cache 目录中的文件,就要求程序开发时,“必须提供 cache 目录的清理解决方案”

      • Caches:存放体积大又不需要备份的数据

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

  • Tmp:临时文件,系统会自动清理。重新启动就会清理。保存应用程序运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。

    • 重新启动手机,系统磁盘空间不足时,该目录会被系统自动清空
    • 存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除。
    • 保存临时文件,后续不需要使用的文件。

4324324

应用沙盒目录的常见获取方式

获取沙盒的根目录:

NSString *home = NSHomeDirectory();

每次编译代码会生成新的沙盒路径,是在编译的时候,所以模拟机和真机每次的沙盒路径都是不一样的。

上面的代码得到的就是当前应用程序目录的路径,该目录下就是应用程序的沙盒,在该目录下有4个文件夹:DocumentsLibrarySystemDatatmp当前应用程序只能访问该目录下的文件。

利用 NSSearchPathForDirectoriesInDomains 函数获取沙盒目录

//文件路径搜索
FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

该方法返回值为一个数组,在iphone中由于只有一个唯一路径,所以直接取数组第一个元素即可。

  • 参数一 NSSearchPathDirectory directory:指定搜索的目录名称,比如这里用NSDocumentDirectory表明我们要搜索的是Documents目录。如果我们将其换成NSCachesDirectory就表示我们搜索的是Library/Caches目录。

  • 参数二 NSSearchPathDomainMask domainMask:搜索主目录的位置,NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。还可以写成NSLocalDomainMask(表示/Library)、NSNetworkDomainMask(表示/Network)等。

  • 参数三 BOOL expandTilde:是否获取完整的路径,该值为YES即表示写成全写形式,为NO就表示直接写成“~”。

上述两个参数的枚举值:

typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) {NSUserDomainMask = 1,       // 用户目录 - 基本上就用这个。 NSLocalDomainMask = 2,      // 本地NSNetworkDomainMask = 4,    // 网络 NSSystemDomainMask = 8,     // 系统NSAllDomainsMask = 0x0ffff  // 所有 
};//常用的NSSearchPathDirectory枚举值
typedef NS_ENUM(NSUInteger, NSSearchPathDirectory) {NSApplicationDirectory = 1,             // supported applications (Applications)NSDemoApplicationDirectory,             // unsupported applications, demonstration versions (Demos)NSAdminApplicationDirectory,            // system and network administration applications (Administration)NSLibraryDirectory,                     // various documentation, support, and configuration files, resources (Library)NSUserDirectory,                        // user home directories (Users)NSDocumentationDirectory,               //  Library 下的(Documentation)模拟器上没有创建NSDocumentDirectory,                    // documents (Documents)
};

获取示例:

 // 搜索 Document 目录NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *documentDirectory = [documentPaths firstObject];NSLog(@"Document Directory: %@", documentDirectory);// 搜索 Document 目录 NONSArray<NSString *> *documentPathsNO = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);NSString *documentDirectoryNO = [documentPathsNO firstObject];NSLog(@"Document Directory NO: %@", documentDirectoryNO);

NSCache

NSCache是苹果提供的一套缓存机制,用法和NSMutableDictionary类似,在AFNetworking,SDWebImage,Kingfisher中都有使用。

当内存不足时NSCache会自动释放内存。 NSCache设置缓存对象数量和占用的内存大小,当缓存超出了设置会自动释放内存。

NSCache是Key-Value数据结构,其中key是强引用,不实现NSCoping协议,作为key的对象不会被拷贝。

NSCache属性

countLimit: 能够缓存对象的最大数量,默认值是0,没有限制。

totalCostLimit: 设置缓存占用的内存大小 evictsObjectsWithDiscardedContent: 是否回收废弃内容,默认YES

NSCache的方法

objectForKey: 通过key获得缓存对象。

setObject: forKey: 缓存对象。

setObject: forKey: cost: 缓存对象,并指定key值对应的成本,用于计算缓存中所有对象的总成本。

removeObjectForKey: 删除指定对象。

removeAllObjects: 删除所有缓存对象。

NSCacheDelegate代理:

willEvictObject: 缓存对象即将被清理时调用,一般开发者用来调试,不能在此方法中修改缓存

在以下场景中会被调用:

  1. removeObjectForKey
  2. 缓存对象超过NSCache的countLimit和otalCostLimit属性设置的限制
  3. App进入后台
  4. 系统发出内存警告
  5. cache这个实例的生命周期结束前
注意
  • 当收到内存警告,而我们又调用removeAllObjects,则无法再继续往内存中添加数据。
  • 不提供缓存总的大小,想知道NSCache占用的内存大小,只有通过添加缓存的cost自己计算。
  • NSCache自动释放内存的算法是不确定的,有时是按照LRU(最近最久未使用)释放,有时随机释放。
  • NSCache中的数据在APP重启后会消失,因为NSCache只是将数据保存在内存 的。

NSCache和NSMutableDictionary的区别
NSCache是线程安全的,不需要加线程锁,而NSMutableDictionary线程不安全。

我们来做个代码演示:

- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.self.view.backgroundColor = [UIColor whiteColor];self.myCache = [[NSCache alloc]init];self.myCache.delegate = self;for (int i = 0; i<10; i++) {[self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1];}for (int i = 0; i<10; i++) {NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);}/// 清除缓存[self.myCache removeAllObjects];/// 设置缓存限制self.myCache.totalCostLimit = 5;NSLog(@"设置缓存限制后=================");for (int i = 0; i<10; i++) {// 设置成本数为1[self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1];}for (int i = 0; i<10; i++) {NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);}/// 清除缓存[self.myCache removeAllObjects];NSLog(@"设置缓存限制后但未设置成本数cost=================");for (int i = 0; i<10; i++) {[self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i)];}for (int i = 0; i<10; i++) {NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);}/// 清除缓存[self.myCache removeAllObjects];}
// 即将回收对象的时候进行调用,实现代理方法之前要遵守NSCacheDelegate协议。
- (void)cache:(NSCache *)cache willEvictObject:(id)obj{NSLog(@"NSCache回收---%@", obj);
}

请添加图片描述
在这里插入图片描述
在这里插入图片描述

从打印结果可以看看出:

  1. 设置缓存限制且知道缓存成本数时,超出是会自动回收。但是设置缓存限制但不知道缓存成本数时不会自动回收。
  2. 使用 removeAllObjects 回收时会调用 willEvictObject 的代理方法。

持久化数据存储方式

Plist(属性列表)

属性列表是一种XML格式的文件,拓展名为plist如果对是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用 writeToFile:atomically: 方法直接将对象写到属性列表文件中。

属性列表与NSDictionary的存储和读取过程:

img

我们用代码学习一下:

- (void)directorfile {// 获取 Document 目录NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *documentDirectory = [documentPaths firstObject];// 在 Document 目录下新建一个 test.plist 文件NSString *documentfileName = [documentDirectory stringByAppendingPathComponent:@"data.plist"];NSLog(@"%@", documentfileName);// // 存字典,将字典数据存到刚才的 test.plist 文件NSDictionary *dict = @{@"name": @"GuiCi", @"boom": @"666"};[dict writeToFile:documentfileName atomically:YES];// 取字典NSDictionary* msgDict = [NSDictionary dictionaryWithContentsOfFile:documentfileName];NSLog(@"%@", msgDict);
}

在这里插入图片描述

Preference 偏好设置(UserDefaults)

大部分iOS应用都支持偏好设置。比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能。

每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。

比如:如果我们要保存用户名,字体大小,是否自动登录:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"张三" forKey:@"username"];
[defaults setFloat:18.0f forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];

当我们需要使用时,读取上次保存的位置:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey:@"username"];
float textSize = [defaults floatForKey:@"text_size"];
BOOL autoLogin = [defaults boolForKey:@"auto_login"];

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

偏好设置存储的优点:

  • 不需要关心文件名,系统会自动帮你生成一个文件名。

  • 快速做键值对的存储。

NSKeyedArchiver 归档解档

之前对于数组的完全深拷贝,我们知道可以使用归档进行,现在我们来真正了解归档吧:

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

但是在我们使用归档之前,我们必须得遵守NSSecureCoding协议才行,老版本只需要遵循NSCoding实现其归档和解档的方法就行,但是iOS13更新之后就不行了,我们就必须的遵守NSSecureCoding协议,NSSecureCoding协议也遵循了原来NSCoding这个协议,不过我们还需要遵循它的一个supportsSecureCoding方法,这样我们才能归档成功。

也就是说我们一定要实现下面三个方法,才可以完成一次归档和解档

- (void)encodeWithCoder:(NSCoder *)coder; // 解码
- (nullable instancetype)initWithCoder:(NSCoder *)coder; // 编码
+ (BOOL)supportsSecureCoding; // 是否支持
// Person.m
#import "Person.h"@implementation Person- (instancetype)initWithName:(NSString *)name age:(NSInteger)age address:(NSString *)address {self = [super init];if (self) {_name = [name copy];_age = age;_address = [address copy];}return self;
}#pragma mark - NSSecureCoding
//一定要返回YES才可以成功归档
+ (BOOL)supportsSecureCoding {return YES;
}- (void)encodeWithCoder:(NSCoder *)coder {[coder encodeObject:_name forKey:@"name"];[coder encodeInteger:_age forKey:@"age"];[coder encodeObject:_address forKey:@"address"];
}- (instancetype)initWithCoder:(NSCoder *)coder {self = [super init];if (self) {_name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];_age = [coder decodeIntegerForKey:@"age"];_address = [coder decodeObjectOfClass:[NSString class] forKey:@"address"];}return self;
}- (NSString *)description {return [NSString stringWithFormat:@"Name: %@, Age: %ld, Address: %@", _name, (long)_age, _address];
}// 获取沙盒Documents目录路径
NSString* getDocumentsPath() {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);return [paths firstObject];
}// 归档(保存)Person对象到指定文件路径
BOOL archivePerson(Person *person, NSString *filePath) {NSError *archiveError = nil;if (@available(iOS 11.0, macOS 10.13, *)) {NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:&archiveError];if (archiveError) {NSLog(@"归档失败: %@", archiveError);return NO;} else {BOOL success = [data writeToFile:filePath atomically:YES];NSLog(@"归档%@", success ? @"成功" : @"失败");return success;}} else {BOOL success = [NSKeyedArchiver archiveRootObject:person toFile:filePath];NSLog(@"归档%@", success ? @"成功" : @"失败");return success;}
}// 解档(读取)Person对象从指定文件路径
Person* unarchivePerson(NSString *filePath) {NSError *unarchiveError = nil;if (@available(iOS 11.0, macOS 10.13, *)) {NSData *data = [NSData dataWithContentsOfFile:filePath];if (data) {Person *person = [NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:data error:&unarchiveError];if (unarchiveError) {NSLog(@"解档失败: %@", unarchiveError);return nil;} else {NSLog(@"解档成功: %@", person);return person;}}return nil;} else {Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];NSLog(@"解档%@%@", person ? @"成功: " : @"失败: ", person);return person;}
}@end

以下简单理解:

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Person : NSObject<NSSecureCoding>
@property (nonatomic, strong) NSString *name;
@property (assign) int age;
- (void)saveData;
- (void)readData;@end#import "Person.h"@implementation Person
- (void)encodeWithCoder:(NSCoder *)coder {[coder encodeObject:_name forKey:@"name"];[coder encodeInt:_age forKey:@"age"];
}
- (id)initWithCoder:(NSCoder *)coder {if (self = [super init]) {_name = [coder decodeObjectForKey:@"name"];_age = [coder decodeIntForKey:@"age"];}return self;
}
// 返回YES才能成功归档
+ (BOOL)supportsSecureCoding {return YES;
}- (void)saveData {Person *p = [[Person alloc] init];p.name = @"clearlove";p.age = 24;NSError *error;// 2.归档模型对象// 3.获得 Documents 的全路径NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];// 4.获得新文件的全路径,即新建一个 person.data 文件来存储我们要归档的数据NSString *path = [docu stringByAppendingString:@"/person.plist"];NSLog(@"%@", path);// 5.将对象封装为 Data 数据并归档NSData *data = [NSKeyedArchiver archivedDataWithRootObject:p requiringSecureCoding:YES error:&error];if (error) {NSLog(@"sodufosuf%@", error);}[data writeToFile:path atomically:YES];
}
// 读档,将数据从文件中读出
- (void)readData {NSError *error;// 1.获得 Documents 的全路径NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];// 2.获得文件的全路径,即获取我们要解档文件的路径NSString *path = [docu stringByAppendingString:@"/person.plist"];// 3.从 path 路径中获取 Data 数据NSData *unData = [NSData dataWithContentsOfFile:path];// 4.从文件中读取Person对象Person *person = (Person *)[NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:unData error:&error];// 打印结果NSLog(@"name: %@ age: %d",person.name, person.age);
}
@end

如果父类也遵守了NSCoding协议,请注意: 应该在encodeWithCoder:方法中加上一句[super encodeWithCode:encode];确保继承的实例变量也能被编码,即也能被归档。应该在initWithCoder:方法中加上一句self = [super initWithCoder:decoder];确保继承的实例变量也能被解码,即也能被恢复。

有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象。 NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间。

// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存档完毕(一定要调用这个方法)
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];

img

数据库存储:
  • SQLite:是目前主流的嵌入式关系型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。

  • CoreData:苹果基于sqlite封装的ORM(Object Relational Mapping)的数据库,直接对象映射.其中有三个对象

    • NSManagedObject
      只要定义一个类继承该类就会创建一张与之对应的表,也就是一个继承与该类的类就对应一张表,每一个通过继承该类创建出来的对象,都是该类对应的表中的一条数据。
    • NSManagedObjectContext
      用于操作数据库,只要有类它就能对数据库的表进行增删改查
    • NSPersistentStoreCoordinator
      决定存储的位置
    • 注意: 该数据库多线程并不安全。最好一个线程创建一个NSManagedObjectContext
  • FMDB:iOS端github使用最广的针对OC对sqlite的封装,支持队列操作

  • WCDB:微信技术团队开源的对sqlite操作的封装,支持对象和数据库映射,ORM数据库的一种实现,比FMDB更高效

序列化与反序列化

序列化:把对象转化为字节序列的过程
反序列化:把字节序列恢复成对象
作用:把对象写到文件或者数据库中,并且读取出来

iOS中怎么实现序列化?

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

http://www.dtcms.com/a/319285.html

相关文章:

  • 【0基础3ds Max】菜单栏介绍
  • 【分享】我国八大沙漠空间矢量范围
  • Bonree ONE发布直通车 | 可观测平台如何深度应用LLM技术
  • 如何科学选择光伏运维系统?
  • docker安装半本地化安装方法
  • Shuffle SOAR使用学习经验
  • FreeRTOS2
  • 4G/5G无线电单元系统
  • 水下管道巡检机器人cad【10张】三维图+设计说明书
  • ai短视频与真人短视频哪个更好?
  • Docker容器部署harbor-小白级教学
  • Aurora MySQL 8.0 性能分析账号创建完整指南
  • ego-planner代码个人阅读笔记
  • 智慧物流分拣效率↑40%:陌讯多模态融合算法实战解析
  • Spring AI Alibaba 项目接入阿里云百炼平台大模型
  • leetcode-hot-100 (技巧)
  • STM32 HAL库外设编程学习笔记
  • SpringBoot中的单例注入方式
  • 上位机知识篇---AT指令
  • 「日拱一码」045 机器学习-因果发现算法
  • C 语言第 17 天学习笔记:从二级指针到内存布局的进阶指南
  • 力控汽车零部件冲压MES系统方案
  • 2025最新国内服务器可用docker源仓库地址大全(2025年8月更新) · DockerHub镜像加速全面指南
  • STM32学习笔记4-OLED外部中断和中断系统
  • nlp-句法分析
  • 虚幻GAS底层原理解剖八 (自定义子类)
  • nohup 学习笔记
  • AWS RDS自定义终端节点深度分析工具:Python脚本详解
  • PyTorch 核心三件套:Tensor、Module、Autograd
  • 旅游mcp配置(1)