目录
1.plist(属性列表)文件
2.Preference\NSUserDefaults(偏好设置)
3.NSKetedArchiver(归档 - 解归档)
4.SQLite 与 FMDB
5.CoreData
6.钥匙串 Keychain
7.云服务 iCloud
8.YYCache 缓存
9.bbx 司机端举例
数据存储缓存基础知识
数据存储也叫数据缓存也叫数据持久化。所谓的持久化就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在 iOS 开发中有很多数据持久化的方案,比如 plist、偏好设置、归档 - 解归档、SQLite、FMDB、CoreData、
Keychain 等。内存缓存:也叫网络缓存;是 App 在运行的时候用到的,你把他想成单例的存放。即指当前程序运行空间,内存缓存速度快容量小,它是供 cpu 直接读取,比如我们打开一个程序,他是运行在内存中的,关闭程序后内存又会释放。内存缓存的数据只保留在APP启动时,比如保存一些从服务端获取到的数据,来缓解服务器的压力,并且节约了用户流量和时间,提高了用户使用体验。iOS 内存分为5个区:栈区,堆区,全局区,常量区,代码区。
硬盘缓存:也叫本地缓存,还叫磁盘缓存;就是存在沙盒。磁盘存储又可以分为文件系统存储和数据库系统存储。
相关链接:
https://www.jianshu.com/p/c7c867646894
https://juejin.cn/post/6844903593913352206
1.内存分区
2.内存缓存和磁盘缓存的区别
缓存分为内存缓存和磁盘缓存两种。
1.内存是指当前程序的运行空间,缓存速度快容量小,是临时存储文件用的,供 CPU 直接读取,比如说打开一个程序,他是在内存中存储,关闭程序后内存就又回到原来的空闲空间。内存缓存适合存储一些 app 高频次使用,并且所占空间不大的文件,比如 NSURLConnection 默认会缓存资源在内存。
2.磁盘是程序的存储空间,缓存容量大、读取速度慢、可持久化。与内存不同的是磁盘是永久存储东西的,只要里面存放东西,不管运行不运行 ,他都占用空间,磁盘缓存是存在 Library/Caches。磁盘存储可以存储一些需要持久化的文件,信息等,简单的讲就是 app 被杀死以后,文件仍然在。
3.沙盒详解
沙盒机制:iOS 为不同数据管理对存储路径做了规范,即在 iOS 中每个 APP 都拥有自己的沙盒,APP 只能访问对应沙盒中存储的数据,iOS 是不允许跨越沙盒去访问数据的,所有的数据都是保存在该沙盒的三个子目录下:Document、Library(Library/Caches,Library/Preference)、temp。
说明:
1.Bundle 和沙盒(sandbox)之间的区别:Bundle 是应用程序在手机中的安装路径。沙盒(sandbox)是专门来存储当前APP自己的数据的路径。
2.Application(应用程序包):包含了所有的资源文件和和可执行文件,上架前经过数字签名,上架后不可修改。
3.沙盒根目录获取:NSString *home = NSHomeDirectory();
相关链接:https://www.jianshu.com/p/34cda6a121db
4.缓存做什么?
我们使用场景比如:离线加载,预加载,本地通讯录...等,对非网络数据,使用本地数据管理的一种,具体使用场景有很多。
5.NSCache
NSCache 是苹果提供的一套缓存机制,用法和 NSMutableDictionary 类似,在
AFNetworking,SDWebImage,Kingfisher 中都有用到。它相当于字典的安全版本,不会出现多线程的问题,线程安全的。说明:
1.当内存不足时 NSCache 会自动释放内存,NSCache 设置缓存对象数量和占用的内存大小,当缓存超出了设置会自动释放内存。
2.NSCache 是 Key-Value 数据结构,其中 key 是强引用,不实现 NSCoping 协议,作为 key 的对象不会被拷贝。
- 举例:
@interface NSCacheVC ()
@property (nonatomic, strong) NSCache *myCache;
@end
@implementation NSCacheVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor purpleColor];
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);
}
@end
1.plist(属性列表)文件
Plist:是一种明文的轻量级存储方式,最常用的格式是 XML 格式,比如新建一个项目时系统会提供一个 info.plist 文件,这种方式的安全性很低,一般使用 plist 文件存储的数据都是不需要加密,存储少量,简单的不重要数据,plist 文件能存储字典和数组,不能存储自定义对象。
说明:
属性列表是一种 XML 格式的文件,拓展名为 plist 如果是对 NSString、NSDictionary、NSArray、NSData、NSNumber 等类型以及他们的可变类型等,就可以使用 writeToFile:atomically: 方法直接将对象写到属性列表文件中存储,读取时使用 arrayWithContentsOfFile:(数组)、dictionaryWithContentsOfFile(字典)等方法。
使用场景:
主要是用于不用加密的数据存储,比如在添加地址中使用的 pickerView 中的 cities.plist 文件。
举例:
/**
* Plist文件
*/
- (void)setUpPlist {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [path stringByAppendingPathComponent:@"Person.plist"];
NSDictionary *dict = @{
@"name" : @"William",
@"age" : @18,
@"height" : @1.75f
};
// 将数据写入Plist
[dict writeToFile:filePath atomically:YES];
NSLog(@"%@", filePath);
// 读取plist中的数据
NSDictionary *dicts = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"dict = %@",dicts);
NSString *str = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"str = %@", str);
}
2.Preference\NSUserDefaults(偏好设置)
很多 iOS 应用都支持偏好设置来存储数据,比如保存用户名、密码、字体大小等设置,iOS 提供了一套标准的解决方案来为应用加入偏好设置功能,每个应用都有个 NSUserDefaults 实例,通过它来存取偏好设置。注意偏好设置也不能存储自定义对象。
说明:
1.偏好设置是专门用来保存应用程序的配置信息的, 一般情况不要在偏好设置中保存其他数据。如果利用系统的偏好设置来存储数据,默认就是存储在 Preferences 文件夹下面的,偏好设置会将所有的数据都保存到同一个文件中。使用偏好设置对数据进行保存之后,它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到 Preferences 文件夹下面。
2.如果需要即刻将数据存储,可以使用 [defaults synchronize],因为 UserDefaults 设置数据时不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘,所以调用了 set 方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用 synchornize 方法 [defaults synchornize]; 强制写入。
3.所有的信息都写在一个文件中,对比简单的 plist 可以保存和读取基本的数据类型,获取 NSuserDefaults 保存(读取)数据。
使用场景:
用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或开机后这些数据仍然存在,比如保存用户名、密码。
举例:
3.NSKetedArchiver(归档 - 解归档)
归档:即序列化,将一个 OC 对象转换成 NSData(二进制)的操作就叫做对象的序列化。解归档:即反序列化,将本地的二进制数据转为一个 OC 对象的操作就叫做反序列化。OC 对象需要通过遵守 NSCoding 协议,并且实现协议中的两个方法,才能支持序列化和反序列化操作。注意归档 NSCoding 可以存储自定义对象。
说明:
1.NSKeyedArchiver 如果对象是 NSString、NSDictionary、NSArray、NSData、NSNumber 等类型,可以直接用 NSKeyedArchiver 进行归档和恢复。不是所有的对象都可以直接用这种方法进行归档,只有遵守了 NSCoding 协议的对象才可以,并且实现协议中的两个方法。
2.NSCoding 协议有2个方法:
2.1 encodeWithCoder: 每次归档对象时都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用 encodeObject:forKey: 方法归档实例变量。
2.2 initWithCoder: 每次从文件中恢复(解码)对象时都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用 decodeObject:forKey 方法解码实例变量。3.如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法,确保继承的实例变量也能被编码,即也能被归档,以及也能被解码,即也能被恢复。
4.将各种类型的对象存储到文件中,不仅仅是字符串或者字典,还能实现对自定义类的对象进行归档。
5.NSKeyedArchiver 是一种轻量级存储的持久化方案,数据化归档时经过加密处理的,所以安全性远高于 plist,数据归档可以存储一些复杂的对象,数据保存前会经过二进制处理。
6.缺点:它的局限是一次性读取和存储操作。归档的形式来保存数据只能一次性归档保存以及一次性解压。所以只能针对小量数据,如果想改动数据的某一小部分,需要解压整个数据或者归档整个数据。
使用场景:
当我们想要存储一些更加复杂的自定义数据时,如果我们的程序此时不需要执行查询数据、数据迁移等操作,或者是并非所有的数据都有发杂的关系图,这时候数据的归档方案是最适合的。使用 archive 格式的文件将归档的数据存储在沙盒目录下,这种格式读取出来的是二进制,需要用 NSKeyedUnarchiver 类对该数据进行解档。
举例:
举例3:
Student.h
#import
#import "Book.h"
@interface Student : NSObject
/**姓名*/
@property(nonatomic, copy) NSString *name;
/**年龄*/
@property(nonatomic, assign) int age;
/**课本*/
@property(nonatomic, strong) Book *book;
@end
Student.m
#import "Student.h"
static NSString * const kStudentNameKey = @"kStudentNameKey";
static NSString * const kStudentAgeKey = @"kStudentAgeKey";
static NSString * const kBookKey = @"kBookKey";
@implementation Student
/**解档(反序列化)*/
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self == [super init]) {
self.name = [aDecoder decodeObjectForKey:kStudentNameKey];
self.age = [aDecoder decodeIntForKey:kStudentAgeKey];
self.book = [aDecoder decodeObjectForKey:kBookKey];
}
return self;
}
/**归档(序列化)*/
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 归档姓名(字符串对象)
[aCoder encodeObject:self.name forKey:kStudentNameKey];
// 归档年龄(注意:这是基本数据类型, 如果是其他的类型,直接调用对应类型的encode即可)
[aCoder encodeInteger:self.age forKey:kStudentAgeKey];
// 归档自定义类(Book)对象
[aCoder encodeObject:self.book forKey:kBookKey];
}
@end
/***************************************/
viewController.m
// 保存
- (IBAction)saveDatas:(id)sender {
// 创建对象
Student *student = [[Student alloc] init];
student.name = @"Alex";
student.age = 15;
student.book.bookName = @"习近平中国特色社会主义";
// 获取文件路径
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [path stringByAppendingPathComponent:@"student.archiver"];
// 将自定义的对象保存到文件中,调用的是NSKeyedArchiver的类方法
BOOL flag = [NSKeyedArchiver archiveRootObject:student toFile:filePath];
if (flag) {
NSLog(@"如果来到这里说明归档成功");
}
}
// 读取
- (IBAction)readDatas:(id)sender {
// 获取文件保存的路径
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [path stringByAppendingPathComponent:@"student.archiver"];
// 打印文件保存的路径
NSLog(@"%@", filePath);
// 从文件中读取对象, 解档对象就调用NSKeyedUnarchiver的一个类方法,unarchiveObjectWithFile: 即可
Student *student = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (student) {
// 来到这里说明读取成功
NSLog(@"%@, %d, %@", student.name, student.age, student.book.bookName);
}
}
4.SQLite 与 FMDB
https://juejin.cn/post/7156037635943694349
- SQLite
SQLite 是嵌入式关系型数据库,是一个轻量级跨平台的小型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。虽然 SQLite 是一款轻型数据库,但是其功能也绝不亚于很多大型关系数据库。SQLite3:操作数据比较快、可以局部读取、比较小型,占用的内存资源比较少。splite 可移植性比较高,有着和 MySpl 几乎相同的数据库语句,它的处理速度比 Mysql、PostgreSQL 这两款著名的数据库都还快,以及无需服务器即可使用的优点。
相关链接:https://juejin.cn/post/6844903793327341575
数据库(SQLite)优缺点
优点
1.该方案可以存储大量的数据,存储和检索的速度非常快。
2.能对数据进行大量的聚合,这样比起使用对象来讲操作要快。缺点
1.它没有提供数据库的创建方式
2.它的底层是基于C语言框架设计的,没有面向对象的 API,用起来非常麻烦。
3.发杂的数据模型的数据建表,非常麻烦。在实际开发中我们都是使用的是 FMDB 第三方开源的数据库,该数据库是基于 splite 封装的面向对象的框架。
- FMDB
FMDB 是 iOS 平台的 SQLite 数据库框架,FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API 即基于 SQLite3 封装的一套 OC 的 API 库。
其优点:
1.使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码。
2.对比苹果自带的 CoreData 框架,更加轻量级和灵活。
3.提供了多线程安全的数据库操作方法,有效地防止数据混乱。相关链接:
https://juejin.cn/post/6844903805369188359
https://juejin.cn/post/7075274270472945671
5.CoreData
coreData 是苹果官方在 iOS5 之后推出的综合性数据库,其使用了对象关系映射技术,将对象转换成数据,将数据存储在本地的数据库中。coreData 为了提高效率需要将数据存储在不同的数据库中,比如在使用的时候,最好是将本地的数据保存到内存中,这样的目的是访问速度比较快。CoreData 本身并不是一个并发安全的架构,所以在多线程中实现 CoreData 会有问题。
相关链接:
https://juejin.cn/post/7011708888718245925
http://t.zoukankan.com/HJiang-p-7818418.html
6.钥匙串 Keychain
keychain 存储在硬盘上,删除了应用,保存的数据还在。每个 APP 的 keychain 相对来说是独立的,但是也可以实现 APP 之间 keychain 数据的共享,前提是同一个 TeamID 下、且设置了数据共享。
相关链接:
https://juejin.cn/post/6844903921765318669
https://juejin.cn/post/6952697138656575496
https://juejin.cn/post/6903710640817307655
7.云服务 iCloud
iCloud 文档存储功能来满足应用数据云存储的需求,用户可以在自己 iCloud 账号下的任何设备访问或修改 App 的这部分数据。iOS iCloud 存储中,苹果提供了三个功能:Key-value storage、iCloud Documents、CloudKit。iCloud 存储空间就是苹果设备 icloud 云端数据存储的空间,属于一种云存储,一般会自带 5GB 的存储空间,若用户需要更大的存储空间,是可以选择购买的。
说明:
- iCloud 可以在iOS 设备和 iPadOS 设备开启、锁定并连接到电源时,通过 Wi-Fi 自动备份您的设备,用户可以使用 iCloud 云备份来恢复 iOS 或 iPadOS 设备,或者无缝设置新设备。
2.查看在 iOS 设备、iPadOS 设备和 Mac 上打开的网站(iCloud 标签页)。即使在离线状态下,也可以阅读阅读列表中的文章。另外也可以在 iOS 设备、iPadOS 设备、Mac 和 Windows 电脑上使用相同的书签。请参阅 Safari 浏览器中的 iCloud 标签页、书签和阅读列表。
相关链接:
https://juejin.cn/post/6844903615723733000
https://baijiahao.baidu.com/s?id=1719371222823021907&wfr=spider&for=pc
8.YYCache 缓存
相关链接:
https://juejin.cn/post/6844903776113917966
https://juejin.cn/post/6885605205380562952
https://juejin.cn/post/6844903694341767182
9.bbx 司机端举例
举例1:新订单有个提示语新字
场景1:在任务列表接到新订单或者在 App 中收到新订单推送的时候,在任务列表页面订单 cell 上面会提示有个新字,地图页面气泡有个新字,点击操作改订单(滑动或者打电话、导航..)则表示已经操作过不是新订单了则去掉新字。这个通过判断订单 id 来区别,可以在订单列表绑定的数据模型里面封装:通过 id 添加到新订单列表、判断是否是新订单、从新订单 id 列表中移除指定订单 id 等方法。
应用1 - 判断是否是新订单:isNewOrder
应用2 - 添加到新订单列表:addIntoNewOrderList
应用3 - 从新订单 id 列表中移除指定订单 id :removeFormNewOrderList
举例2:成都机场提示
场景2:定位成都的司机,未报班状态下访问首页,新增“成都地区有新投运机场”相关提示。
1.提示内容:成都地区存在新投运机场,请您在接到相关订单后及时跟乘客确认目的地,以免走错机场。
2.展示逻辑:优化上线后推送给定位为“成都”的司机,司机非报班状态下可见此提示,司机点击“X”后隐藏不再展示横幅。
3.注意2种情况:
第一种是同一部手机多次登录同一个账号或者同一部手机登录多个账号的时候,点击 X 关闭提示语会记录关闭次数,展示的时候需要判断当前账号与关闭账号是否是同一个账号。如果关闭次数大于0,判断如果是同一个账号则表示之前展示过且被关闭过则不需要展示,如果不是同一个账号则表示关闭的是其他账号,当前账号没有被关闭过没有关闭次数即未展示过或者展示过但未被关闭过,这个时候需要展示。
第二种情况是同一个账号被多部手机登录的时候,如果关闭次数等于0,即第一次进来页面未报班的时候都展示。即使账号在 A 手机登录展示过被关闭过,再在 B 手机上登录一样需要重新走 A 的逻辑,第一次进来页面未报班的时候还是需要展示提示语,即这个缓存以手机为准,不以账号为准。
举例3:订单有个提示标签
场景3:如果是新用户即首次下单的用户,对于货件订单且订单备注包含类型“手机数码、数码产品、大物件、其它、其他”等,如果订单状态为上车了则不展示,如果是还没有上车分2种情况,第一种是之前没有展示过或者展示过没有关闭则展示,第二种是之前展示过且被关闭过则不展示提示标签。
注意:
1.如果账号在手机 A 登录有展示标签,点击关闭后再在手机 B 登录发现也会有标签,即缓存跟手机有关跟账号没有关系,两个手机独立的,相当于各自都是第一次走逻辑互不影响。
2.如果账号在手机上登录有展示标签,点击关闭不展示标签。这个时候 App 卸载重新安装,新装的 App 相当于第一次重新走逻辑会展示标签。