文章目录
一. 数据持久化的目的
- 快速战术,提升体验。
- 已经加载过的数据,用户下次查看的时候不需要再次从网络磁盘加载,直接展示给用户。
- 节省用户流量
- 对于较大的资源数据进行缓存,下次展示无需下载消耗流量。
- 同时也降低了服务器的访问次数,节省服务器的资源。
- 方便离线使用
- 用户浏览过的数据无需互联网就可以再次查看。
- 部分功能使用解除了对于网络的依赖(离线地图)
- 没有网络的时候允许用户进行操作,等到下次联网的时候同步到服务器
- 记录用户操作
- 草稿:对于用户需要花费较大的成本进行的操作,对用户的每个步骤进行缓存,当用户中断操作的时候记录,下次操作直接继续上次的操作。
- 已读内容的标记,帮助用户识别哪些已读
- 搜索记录缓存。
二. iOS中数据持久化方案
NSUserDefault
:简单数据快速读写。Property list
: 属性列表的文件存储Archiver
:归档。SQLite
:本地数据库。CoreData
:CoreData 是基于 sqlite 的封装。
三. 数据持有化方式的分类
在移动端的数据持有化方式总体两类:
1. 内存缓存
- 定义: 对于使用频率比较高的数据,从网络或者磁盘加载数据到内存以后,使用但是不马上销毁,下次直接从内存加载
内存指当前程序的运行空间,缓存速度快容量小,是临时存储文件用的,供CPU直接读写。打开一个程序,他是在内存中存储,关闭程序后内存就又回到原来的空间。
例如:
- iOS图片初始化方法:
[UIImage imageNamed:@“imageName”];
- 网络图片加载的第三方库
SDWebImage
2. 磁盘缓存
- 定义:将从网络加载的以及用户操作产生的数据写入到磁盘方便用户下次查看,继续操作的时候从磁盘直接加载使用。
磁盘是程序的存储空间,缓存容量大,速度慢,可持有化,和内存的区别是磁盘可以永久的存储东西。
例如:
- 用户输入操作内容的缓存(CSDN的草稿)
- 搜索历史的缓存
- 网络图片加载第三方
SDWebImage
SDWebImage缓存
对于SDWebImage的缓存方式:SDWebImage
的图片缓存采用的是 Memory
和 Disk
双重Cache
机制,也就是二级缓存。 SDWebImage
的缓存机制可以简单概括为:第一次请求加载图片后将图片缓存在内存中,并通过md5加密将图片的url进行加密处理,加密处理后的值作为key,将图片存储在磁盘中。当再次加载图片时,先在内存缓存中寻找,如果内存中找不到图片,则在默认的磁盘中寻找,如果还是找不到,则再请求加载图片。
四. 沙盒机制的介绍
沙盒机制:iOS中的沙盒机制是一种安全体系。为了保证系统安全,iOS每个应用程序在安装时,会创建属于自己的沙盒文件(存储空间)。
- 应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此,
- 例如属性文件
plist
、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。
五. 沙盒目录结构
每个APP的沙盒内部都有类似的目录结构 Apple的沙盒描述
1. 获取应用程序的沙盒路径
// 获取沙盒根目录路径
NSString *path = NSHomeDirectory();
⚠️注意:每次编译代码会生成新的沙盒路径,是在编译的时候,所以模拟机和真机每次的沙盒路径都是不一样的。
上面的代码得到的就是当前应用程序目录的路径,该目录下就是应用程序的沙盒,在该目录下有4个文件夹:Documents
、Library
、SystemData
、tmp
,当前应用程序只能访问该目录下的文件。
Documents
目录:您应该将所有的应用程序数据文件写入到这个目录下。这个目录用于存储用户数据。该路径可通过配置实现iTunes共享文件。可被iTunes备份。AppName.app
目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。Library
目录:这个目录下有两个子目录:
Preferences
目录:包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好.Caches
目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。
可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。
tmp
目录:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。该路径下的文件不会被iTunes备份。
2. 访问沙盒目录常用C函数介绍
//文件路径搜索
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
:是否获取完整的路径,我们知道在iOS中的全写形式是/User/userName
,该值为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 目录 NO
NSArray<NSString *> *documentPathsNO = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
NSString *documentDirectoryNO = [documentPathsNO firstObject];
NSLog(@"Document Directory NO: %@", documentDirectoryNO);
//
3. 沙盒目录介绍
Documents
:保存持久化数据,会备份。一般用来存储需要持久化的数据。
一般我们在项目中,我们会把一些用户的登录信息以及搜索历史记录等一些关键数据存储到这里。
NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentPaths firstObject];
NSLog(@"Document Directory: %@", documentDirectory);
此文件夹是默认备份的,备份到iCloud。
注:iCloud的备份,会通过Wi-Fi每天自动备份用户iOS设备。
我们可以在获取到的路径结尾加一个字符串来创建一个文件名:
// 创建文件
NSString *documentfileName = [documentDirectory stringByAppendingPathComponent:@"data.txt"];
NSLog(@"%@", documentfileName);
这样我们的这个filename就是一个完整的.txt类型文件的目录了。
Library
:默认存放设置和其他状态信息,除了caches
子目录之外其他目录都会被icloud同步。Application Support
:此目录包含应用程序用来运行但应对用户隐藏的文件,如游戏的新关卡等文件。Caches
:保存应用运行时生成的需要持久化的数据,一般存储体积大、不需要备份的非重要数据,如网络请求的音视频与图片等的缓存。在 iOS 5.0 及以后版本中,Caches
当系统磁盘空间非常低时,系统可能会在极少数情况下该删除目录(APP 正在运行时不会发生),所以尽量保证该路径的文件在 APP 在重新运行时可以得到重新创建。Cooikes
:系统会自动将App中网络请求的cookie
保存为文件。Preferences
:保存应用的所有偏好设置。UserDefaults
生成的plist
文件就会保存该目录下。SplashBoard
:存储启动屏缓存,缓存文件格式为ktx
,本质上就是图片,如果启动屏不生效的问题可以考虑从删除该路径下相关缓存文件这个角度解决。
SystemData
:存放系统数据,无对外暴露的接口。tmp
:临时文件夹(系统会不定期删除里面的文件)。
六. 持久化数据存储方式
1. XML属性列表
属性列表是一种XML
格式的文件,拓展名为plist
。
如果对象是NSString
、NSDictionary
、NSArray
、NSData
、NSNumber
等类型,就可以使用
writeToFile:atomically:
方法直接将对象写到属性列表文件中,举例说明:
- (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": @"clearlove", @"boom": @"4396"};
[dict writeToFile:documentfileName atomically:YES];
// 取字典
NSDictionary* msgDict = [NSDictionary dictionaryWithContentsOfFile:documentfileName];
NSLog(@"%@", msgDict);
}
同时进入该项目的沙盒目录中可以看到 Document 目录下多了我们刚才创建的 test.plist 文件:
能看到我们存储的字典类型的文件已经存放
2. Preference 偏好设置(UserDefaults)
很多iOS应用都支持偏好设置,提供了一套标准的解决方案来为应用加入偏好设置功能,比如保存用户名,字体大小,密码,是否自动登录等。
每个应用都有个NSUserDefaults
实例,可以通过它来存取偏好设置,不需要路径。其本身的创建类似于单例模式,我们在后面用不同的属性名再次申请创建,会覆盖之前的数据。
NSUserDefaults
:简单数据快速读写,不能存储自定义类型。
UserDefaults
设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize
方法[defaults synchornize];
强制写入。
偏好设置存储的优点:
- 不需要关心文件名,系统会自动帮你生成一个文件名。
- 快速做键值对的存储。
- (void)preference {
// 获取偏好设置对象
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// 存储数据,快速键值对/
[defaults setObject:@"clearlove" forKey:@"username"];
[defaults setObject:@"4396-2016" forKey:@"password"];
[defaults synchronize];
// 需要验证账号密码的地方,获取偏好设置对象
NSUserDefaults *defaultsA = [NSUserDefaults standardUserDefaults];
NSString *name = [defaultsA objectForKey:@"username"];
NSString *password = [defaultsA objectForKey:@"password"];
NSLog(@"name:%@ password:%@", name, password);
}
3. NSKeyedArchiver 归档解档
NSKeyedArchiver(
归档):归档一般都是保存自定义对象的时候,使用归档。因为plist文件不能够保存自定义对象。如果一个字典中保存有自定义对象,如果把这个对象写入到文件当中,它是不会生成 plist文件的。如果对象是NSString
、NSDictionary
、NSArray
、NSData
、NSNumber
等类型,可以直接用NSKeyedArchiver
进行归档和恢复。
但是在我们使用归档之前,我们必须得遵守NSSecureCoding
协议才行,老版本只需要遵循NSCoding
实现其归档和解档的方法就行,但是iOS13更新之后就不行了,我们就必须的遵守NSSecureCoding
协议,NSSecureCoding
协议也遵循了原来NSCoding
这个协议,不过我们还需要遵循它的一个supportsSecureCoding
方法,这样我们才能归档成功。
因为NSSecureCoding
协议也遵循了原来NSCoding
这个协议,所以他也就有了- (void)encodeWithCoder:(NSCoder *)coder
方法和- (id)initWithCoder:(NSCoder *)coder
方法:
- (void)encodeWithCoder:(NSCoder *)coder方法
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量。可以使用
encodeObject:forKey:
方法归档实例变量。
- (id)initWithCoder:(NSCoder *)coder方法
每次从文件中会恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用
decodeObject:forKey
方法解码实例变量。
除了这两个方法之外,NSSecureCoding
协议还有+ (BOOL)supportsSecureCoding
方法,只有当这个方法为YES的时候,才可以归档成功。
利用归档解档尝试存储一个Person对象
#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
- 打印文件路径 查看多了一个person.
Document多了一个plist文件
⚠️注意: 我们一定要遵守NSSecureCoding
协议,并实现它的归档解档方法,以及+ (BOOL)supportsSecureCoding
方法!!!
4. 数据库存储
SQLite
:
是目前主流的嵌入式关系型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。
CoreData
:
CoreData
是iOS5之后才出现的一个框架,本质上是对SQLite
的一个封装,它提供了对象-关系映射(ORM
)的功能,即能够将OC
对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC
对象,在这个过程中不需要手动编写任何SQL
语句,CoreData
封装了数据库的操作过程,以及数据库中数据和OC对象的转换过程。通过CoreData
管理应用程序的数据模型,可以极大程度减少需要编写的代码数量。
FMDB
:
是一个处理数据存储的第三方框架,框架是对sqlite
的封装,整个框架非常轻量级但又不失灵活性,而且更加面向对象。
SQLite
和CoreData
的区别:
- CoreData可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表时,其关联的其他表的也会随着更新。
- CoreData供更简单的性能管理机制,可以限制查询记录的总数,这个类会自动更新其缓存。
- 多表查询方面,CoreData没有SQL直观,没有类似外连接,左连接等操作。
5. 什么是序列化和反序列化? 用来做什么?
- 序列化:把对象转化为字节序列的过程
- 反序列化:把字节序列恢复成对象
- 作用:把对象写到文件或者数据库中,并且读取出来
iOS中怎么实现序列化?
在iOS实际开发中,我们也会使用到序列化,归档就是我们在iOS开发中使用序列化的场景!
在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档(解档)。