实用指南:「iOS」————持久化

实用指南:「iOS」————持久化

iOS学习IOS——持久化为什么持久化?数据持久化方式**数据存储区域**内存缓存磁盘缓存沙盒机制应用沙盒目录的常见获取方式NSCacheNSCache属性NSCache的方法NSCacheDelegate代理:注意持久化数据存储方式Plist(属性列表)Preference 偏好设置(UserDefaults)NSKeyedArchiver 归档解档数据库存储:序列化与反序列化

IOS——持久化为什么持久化?数据保存:

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

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

状态恢复:

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

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

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

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

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

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

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

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

目的:通过缓存数据减少网络请求,提高应用程序的响应速度。示例:缓存图片、API 数据等。数据持久化方式NSUserDefault:简单数据快速读写。

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

Archiver:归档。

SQLite:本地数据库。

CoreData:CoreData 是基于 sqlite 的封装。

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

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

例如:iOS的图片加载

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

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

对比项内存缓存磁盘缓存速度极快较慢容量小(受内存限制)大(受磁盘限制)持久性易失(进程结束即丢失)持久(重启后仍在)适用场景热点、临时、频繁访问数据历史、草稿、长期数据实现难度简单难因此实际开发中,通常采取二级缓存的方法:

先查内存缓存,内存没有再查磁盘缓存,磁盘没有再查网络或重新生成。沙盒机制出于安全的原因,iOS 应用在安装时,为每个 App 分配了独立的目录,App 只能对自己的目录进行操作,这个目录就被称为沙盒。

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

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

沙盒中主要包含4个目录:MyApp.app、Documents、Library、Tmp,目录结构如下:

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

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

Library

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

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

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

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

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

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

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

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

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

NSString *home = NSHomeDirectory();

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

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

利用 NSSearchPathForDirectoriesInDomains 函数获取沙盒目录

//文件路径搜索

FOUNDATION_EXPORT NSArray

*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

*documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentDirectory = [documentPaths firstObject];

NSLog(@"Document Directory: %@", documentDirectory);

// 搜索 Document 目录 NO

NSArray

*documentPathsNO = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);

NSString *documentDirectoryNO = [documentPathsNO firstObject];

NSLog(@"Document Directory NO: %@", documentDirectoryNO);

NSCacheNSCache是苹果提供的一套缓存机制,用法和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: 缓存对象即将被清理时调用,一般开发者用来调试,不能在此方法中修改缓存

在以下场景中会被调用:

removeObjectForKey缓存对象超过NSCache的countLimit和otalCostLimit属性设置的限制App进入后台系统发出内存警告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);

}

从打印结果可以看看出:

设置缓存限制且知道缓存成本数时,超出是会自动回收。但是设置缓存限制但不知道缓存成本数时不会自动回收。使用 removeAllObjects 回收时会调用 willEvictObject 的代理方法。持久化数据存储方式Plist(属性列表)属性列表是一种XML格式的文件,拓展名为plist如果对是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用 writeToFile:atomically: 方法直接将对象写到属性列表文件中。

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

我们用代码学习一下:

- (void)directorfile {

// 获取 Document 目录

NSArray

*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

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@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];

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

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

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

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

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

iOS中怎么实现序列化?

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

相关推荐