1.快速展示,提升体验
2.节省用户流量(节省服务器资源)
3.离线使用。
4.记录用户操作
NSUserDefault
简单数据快速读写Property list
(属性列表)文件存储Archiver
(归档)SQLite
本地数据库CoreData
在移动端的数据持有化方式总体有两类:
内存指当前程序的运行空间,缓存速度快容量小,是临时存储文件用的,供CPU直接读写。打开一个程序,他是在内存中存储,关闭程序后内存就又回到原来的空间空间。
磁盘是程序的存储空间,缓存容量大、速度慢、可持有化。与内存不同的是磁盘是永久存储东西的。
iOS中的沙盒机制是一种安全体系。为了保证系统安全,iOS每个应用程序在安装时,会创建属于自己的沙盒文件(存储空间)。应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此,例如属性文件plist、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。
// 获取沙盒根目录路径
NSString *path = NSHomeDirectory();
注意: 每次编译代码会生成新的沙盒路径,注意是编译不是启动,所以模拟机或者真机运行,每次运行所得到的沙盒路径都是不一样的,线上版本app真机不会生成新的沙盒路径。
上面的代码得到的就是当前应用程序目录的路径,该目录下就是应用程序的沙盒,在该目录下有4个文件夹:Documents
、Library
、SystemData
、tmp
,当前应用程序只能访问该目录下的文件。
//文件路径搜索
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就表示直接写成“~”。
该值为
NO
:Caches目录路径为~/Library/Caches
该值为YES
:Caches目录路径为/var/mobile/Containers/Data/Application/E7B438D4-0AB3-49D0-9C2C-B84AF67C752B/Library/Caches
下面给出上述两个参数的枚举值:
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)
};
//获取沙盒根路径
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);
Documents
:保存持久化数据,会备份。一般用来存储需要持久化的数据。// 获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
此文件夹是默认备份的,备份到iCloud。NSString *filename = [docDir stringByAppendingPathComponent:@"data.txt"];
这样我们的这个filename
就是一个完整的.txt
类型文件的目录了。Library
:默认存放设置和其他状态信息,除了caches子目录之外其他目录都会被iclude同步。
Application Support
:此目录包含应用程序用来运行但应对用户隐藏的文件,如游戏的新关卡等文件。Caches
:保存应用运行时生成的需要持久化的数据,一般存储体积大、不需要备份的非重要数据,如网络请求的音视频与图片等的缓存。在 iOS 5.0 及以后版本中,Caches 当系统磁盘空间非常低时,系统可能会在极少数情况下该删除目录(APP 正在运行时不会发生),所以尽量保证该路径的文件在 APP 在重新运行时可以得到重新创建。Cooikes
:系统会自动将App中网络请求的cookie保存为文件。Preferences
:保存应用的所有偏好设置。UserDefaults 生成的 plist 文件就会保存该目录下。SplashBoard
:存储启动屏缓存,缓存文件格式为 ktx,本质上就是图片,如果启动屏不生效的问题可以考虑从删除该路径下相关缓存文件这个角度解决。SystemData
:存放系统数据,无对外暴露的接口。tmp
:临时文件夹(系统会不定期删除里面的文件)。属性列表是一种XML
格式的文件,拓展名为plist
。
如果对象是NSString
、NSDictionary
、NSArray
、NSData
、NSNumber
等类型,就可以使用
writeToFile:atomically:
方法直接将对象写到属性列表文件中,举例说明:
// 获取 Document 文件目录
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
// 在 Document 目录下新建一个 test.plist 文件
NSString * filePath = [docPath stringByAppendingPathComponent:@"test.plist"];
// 存字典,将字典数据存到刚才的 test.plist 文件
NSDictionary* dict = @{ @"name" :@"xiyouedc", @"age" : @"18" };
[dict writeToFile:filePath atomically:YES];
// 取字典,从刚才的 test.plist 文件中取出字典数据
NSDictionary* dictA = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"%@", dictA);
// 存数组
NSArray* array = @[@"xiyouedc", @"18"];
[array writeToFile:filePath atomically:YES];
// 取数组
NSArray* arrayA = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@"%@", arrayA);
输出结果:
同时进入该项目的沙盒目录中可以看到 Document 目录下多了我们刚才创建的 test.plist 文件:
因为我们最后是存储的 NSArray 数据类型的,所以他这里也就是 NSArray 类型的数据。
很多iOS应用都支持偏好设置,提供了一套标准的解决方案来为应用加入偏好设置功能,比如保存用户名,字体大小,密码,是否自动登录等。
每个应用都有个NSUserDefaults
实例,可以通过它来存取偏好设置,不需要路径。其本身的创建类似于单例模式,我们在后面用不同的属性名再次申请创建,会覆盖之前的数据。
NSUserDefaults
:简单数据快速读写,不能存储自定义类型。
UserDefaults
设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set
方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize
方法[defaults synchornize];
强制写入。
偏好设置存储的优点:
我们使用UserDefaults
注册一个账号密码试一下:
// 获取偏好设置对象
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//存储数据
[defaults setObject:@"xiyouedc" forKey:@"name"];
[defaults setObject:@"1234567890" forKey:@"password"];
// 同步调用,立刻写到文件中,不写这个方法会异步,有延迟
[defaults synchronize];
// 需要验证账号密码的地方,获取偏好设置对象
NSUserDefaults *defaultsA = [NSUserDefaults standardUserDefaults];
NSString *name = [defaultsA objectForKey:@"name"];
NSString *password = [defaultsA objectForKey:@"password"];
NSLog(@"name:%@ password:%@", name, password);
输出结果:
同时我们可以看到在沙盒中的偏好设置中出现了一个新的文件,里边存放的是我们刚才写入的数据:
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.h
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSSecureCoding>
@property (nonatomic, strong) NSString *name;
@property int age;
@end
// person.m
#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;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
// 归档:自定义对象想要存储到沙盒(文件夹),必须通过归档。
- (void)save {
// 1.新的模型对象
Person *person = [[Person alloc] init];
person.name = @"xiyouedc";
person.age = 18;
NSError *error;
// 2.归档模型对象
// 3.获得 Documents 的全路径
NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 4.获得新文件的全路径,即新建一个 person.data 文件来存储我们要归档的数据
NSString *path = [docu stringByAppendingString:@"/person.data"];
// 5.将对象封装为 Data 数据并归档
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:&error];
if (error) {
NSLog(@"sodufosuf%@", error);
}
[data writeToFile:path atomically:YES];
}
// 读档,将数据从文件中读出
- (void)read {
NSError *error;
// 1.获得 Documents 的全路径
NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 2.获得文件的全路径,即获取我们要解档文件的路径
NSString *path = [docu stringByAppendingString:@"/person.data"];
// 3.从 path 路径中获取 Data 数据
NSData *unData = [NSData dataWithContentsOfFile:path];
// 4.从文件中读取Person对象
Person *person = (Person *)[NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:unData error:&error];
// 打印结果
NSLog(@"%@--%d",person.name, person.age);
}
输出结果:
同时 Documents 文件下也出现了我们创建的 person.data 文件:
注意: 我们一定要遵守NSSecureCoding
协议,并实现它的归档解档方法,以及+ (BOOL)supportsSecureCoding
方法!!!
SQLite
:CoreData
:FMDB
:SQLite
和CoreData
的区别:
序列化
:把对象转化为字节序列的过程反序列化
:把字节序列恢复成对象作用
:把对象写到文件或者数据库中,并且读取出来plist
全名Property List
,属性列表文件,它是一种用来存储串行化后的对象的文件,属性列表文件的扩展名为.plist
,因此通常被称为plist
文件。文件是xml
格式的。plist文件通常用于储存用户设置,也可以用于存储捆绑的信息。
看了上面讲解其实已经了解完了,这里补充一下程序中的plist文件:
对应的读取和写入操作:
// 其中,Show为我们plist文件的名称,后面的plist是Show的扩展名
// 写入plist
- (void)setDataFromPlist {
NSString *plist = [[NSBundle mainBundle] pathForResource:@"Show" ofType:@"plist"];
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[temp setValue:@18 forKey:@"age"];
[temp setValue:@"男" forKey:@"sex"];
[temp writeToFile:plist atomically:YES];
}
// 读取plist
- (void)getDataFromPlist {
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Show" ofType:@"plist"];
NSMutableDictionary *dataDic = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
NSLog(@"%@", dataDic);//直接打印数据
}
如果觉得不够深入可以自行了解:iOS数据持久化设计、iOS 数据存储(持久化存储、缓存)