目录
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个区:栈区,堆区,全局区,常量区,代码区。
硬盘缓存:也叫本地缓存,还叫磁盘缓存;就是存在沙盒。磁盘存储又可以分为文件系统存储和数据库系统存储。
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();
4.缓存做什么?
我们使用场景比如:离线加载,预加载,本地通讯录...等,对非网络数据,使用本地数据管理的一种,具体使用场景有很多。
5.NSCache
NSCache 是苹果提供的一套缓存机制,用法和 NSMutableDictionary 类似,在
AFNetworking,SDWebImage,Kingfisher 中都有用到。它相当于字典的安全版本,不会出现多线程的问题,线程安全的。说明:
1.当内存不足时 NSCache 会自动释放内存,NSCache 设置缓存对象数量和占用的内存大小,当缓存超出了设置会自动释放内存。
2.NSCache 是 Key-Value 数据结构,其中 key 是强引用,不实现 NSCoping 协议,作为 key 的对象不会被拷贝。
- 举例:
@interface NSCacheVC ()<NSCacheDelegate>
@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);
}
plist 文件其他补充:
plist文件是将某些特定的类,通过XML文件的方式保存在目录中,可以被序列化的类型只有如下几种:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.获得文件路径:
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
2.存储:
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
3.读取:
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
4.注意:
只有以上列出的类型才能使用plist文件存储,不能直接存储自定义模型对象,如果要自定义则只能将自定义模型对象转换为字典存储。
存储时使用 writeToFile: atomically: 方法,其中 atomically 表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写 YES。
读取时使用 arrayWithContentsOfFile: 方法。
plist 只能识别字典,数组。
2.Preference\NSUserDefaults(偏好设置)
很多 iOS 应用都支持偏好设置来存储数据,比如保存用户名、密码、字体大小等设置,iOS 提供了一套标准的解决方案来为应用加入偏好设置功能,每个应用都有个 NSUserDefaults 实例,通过它来存取偏好设置。注意偏好设置也不能存储自定义对象。
说明:
1.偏好设置是专门用来保存应用程序的配置信息的, 一般情况不要在偏好设置中保存其他数据。如果利用系统的偏好设置来存储数据,默认就是存储在 Preferences 文件夹下面的,偏好设置会将所有的数据都保存到同一个文件中。使用偏好设置对数据进行保存之后,它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到 Preferences 文件夹下面。
2.如果需要即刻将数据存储,可以使用 [defaults synchronize],因为 UserDefaults 设置数据时不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘,所以调用了 set 方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用 synchornize 方法 [defaults synchornize]; 强制写入。
3.所有的信息都写在一个文件中,对比简单的 plist 可以保存和读取基本的数据类型,获取 NSuserDefaults 保存(读取)数据。
4.对于NSUerdefaults来说 一般可以用来保存用户的偏好设置 比如登陆账号 密码这些。 除此之外还可以用它来保存图片, 字符串 , 数字 和对象。它被保存到项目中的Plists文件里面里。保存图片 一般用它里面的两个方法 图片保存可以用PNG或者JPG对应的方法 先转换成NSData 再用NSUerdefaults保存 保存的时候为了让它马上存下来要用synchronize 。它还可以用来在程序间进行反向传值。
5.注意:偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据;如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中,所以如果需要立即写入文件的就必须调用synchronize方法;偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
使用场景:
用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或开机后这些数据仍然存在,比如保存用户名、密码。
举例:
3.NSKetedArchiver(归档 - 解归档)
归档:即序列化,将一个 OC 对象转换成 NSData(二进制)的操作就叫做对象的序列化。解归档:即反序列化,将本地的二进制数据转为一个 OC 对象的操作就叫做反序列化。OC 对象需要通过遵守 NSCoding 协议,并且实现协议中的两个方法,才能支持序列化和反序列化操作。注意归档 NSCoding 可以存储自定义对象。由于决大多数支持存储数据的 Foundation 和 Cocoa Touch 类都遵循了 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:
举例5:
举例6 - NSKeyedArchiver 简单使用说明:
NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
1.遵循NSCoding协议
@interface Person : NSObject <NSCoding>
2.设置属性
@property (strong, nonatomic) UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
3.实现协议方法
解档:
- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
归档:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
特别注意:如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder]
和 [super initWithCoder:aDecoder] 方法;
其他使用说明:
需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法:
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可:
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
4.SQLite 与 FMDB
- SQLite
SQLite 是嵌入式关系型数据库,是一个轻量级跨平台的小型数据库,是一种基于C语言开发的轻型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。虽然 SQLite 是一款轻型数据库,但是其功能也绝不亚于很多大型关系数据库。SQLite3:操作数据比较快、可以局部读取、比较小型,占用的内存资源比较少。splite 可移植性比较高,有着和 MySpl 几乎相同的数据库语句,它的处理速度比 Mysql、PostgreSQL 这两款著名的数据库都还快,以及无需服务器即可使用的优点。
1.在 iOS 中需要用 C 语言进行对数据库的操作、访问(无法用 oc,因为 libsqlite3 框架基于 C 语言编写)。
2.SQLite 采用动态数据类型。
3.要使用 SQLite 很简单,如果在 Mac OSX 上使用可以考虑到 SQLite 官方网站下载命令行工具,也可以使用类似于 SQLiteManager、Navicat for SQLite 等工具。
SQLite 常用命令:
启动 SQLite:在终端键入 sqlite3,提示符变成 sqlite>
.help --- 显示帮助信息
.quit/.exit --- 退出
.open --- 打开指定的数据库文件
.save --- 将内存中的数据库保存到指定的文件
.databases --- 显示数据库的信息
.schema ?TABLE? --- 显示表结构
.tables ?PATTERN? --- 显示所有的表
.mode MODE --- 设置输出模式
.stats ON|OFF --- 开启或关闭统计信息
.dump ?TABLE?--- 以SQL文本格式转存数据库
数据库(SQLite)优缺点:
优点
1.该方案可以存储大量的数据,存储和检索的速度非常快。
2.能对数据进行大量的聚合,这样比起使用对象来讲操作要快。缺点
1.它没有提供数据库的创建方式
2.它的底层是基于C语言框架设计的,没有面向对象的 API,用起来非常麻烦。
3.发杂的数据模型的数据建表,非常麻烦。在实际开发中我们都是使用的是 FMDB 第三方开源的数据库,该数据库是基于 splite 封装的面向对象的框架。
举例 - 以学生表为例讲解相关基本知识:
schema TbStudent查看学生表内容
tables列出所有表
header on 显示表头
—插入数据:
insert into TbStudent values(1001,'王大锤','男','重庆万州','1990-1-6');
insert into TbStudent (stuid,stuname) values(1002,'李林’);//部分项
—查询所有行所有列
select * from TbStudent;
—删除数据(要加条件,否则删除全部;下面同这)
delete from TbStudent where stuid=1004;
—更新数据
update TbStudent set stuaddr='四川绵阳',stubirth='1993.12.3' where stuid=1002;
(其中1002的没有加地址和出生)
--投影操作
select stuname,stusex from TbStudent;
--投影时别名(as可省,别名用法)
select stuname as 姓名 , stusex as 性别 from Tbstudent;
--数据筛选(可加关系:and or)
select *from TbStudent where stusex='女';
select *from TbStudent where stusex='女' and stuaddr='南京';
select *from TbStudent where stusex='女' or stubirth='1998-1-6';
select *from TbStudent where stubirth between '1985-1-1' and '1990-12-12';
select *from TbStudent where stubirth<'1990-12-1';
--模糊查询
select *from TbStudent where stuname like '王%';匹配任意多个字符
select *from TbStudent where stuname like'%王%';只要名字中有王字
select *from TbStudent where stuname like'王_';匹配精确一个字符
select *from TbStudent where stuname like'王__';
--排序(默认是升序的asc(可省);降序desc)[同时用筛选和排序,先筛后排]
select *from TbStudent order by stubirth;
select *from TbStudent order by stubirth desc;
select *from TbStudent order by stusex, stubirth desc;//先排性别后排出生
--统计数
select count(stusex) 总人数 from TbStudent;
--分组
select stusex,count(stusex)from TbStudent group by stusex;//男女各多少人
select stuaddr 家庭住址,count(stuaddr)as 总人数 from TbStudent group by stuaddr order by 总人数;
--聚合函数、分组函数
count
max
min
avg:平均数
sum
-----子查询(把一个查询的结果作为另一个查询的一部分来使用)
select stuname, stubirth from TbStudent where stubirth=(select min (stubirth) from TbStudent);//年龄最大的名字
select min(stubirth) from TbStudent;//选择出年龄最大
—分页查询(limit a,b—>a是第几条,b是从这条开始依次查b个)
select * from tbemp order by sal desc limit 10,5;//第10到14
-------连接查询(一次查询多张表)-----
--->内连接
--->外连接 ---> 左外连接(SQLite只支持左;即不管是否满足连接,都写出来,不满足的地方补空)、右外连接、全外连接
//以TbDept表和TbEmp表为例:
(注意:连接两张表时如果没有连接条件将产生笛卡尔积)
写法1:=
select empno,name,job,dname,dloc from TbEmp, Tbdept where TbEmp.dno =TbDept.deptno;
***
select empno,name,job,dname,dloc from TbEmp as t1, Tbdept as t2 where t1.dno =t2.deptno;
写法2:… inner jion … on
select empno, name, job, dname, dloc from TbEmp, inner join
TbDept on dno = deptno;
***
select empno, name, job, dname, dloc from TbEmp as t1 inner join TbDept as t2 on t1.dno = t2.deptno;
***查询薪水超过其所在部门平均薪水的员工的姓名、部门编号和工资
SELECT ename,deptno,sal from TbEmp as t1 inner join TbDept as t2 on sal>(SELECT avg(sal)from TbEmp where t1.dno=t2.deptno );
***查询部门中薪水最高的人姓名、工资和所在部门名称
SELECT ename,sal,dname from TbEmp as t1 inner join TbDept as t2 on sal=(SELECT max(sal) from TbEmp where t1.dno=t2.deptno);
***注意学会创建临时表
写在前面的是左表;写在后面的右表
- FMDB
FMDB 是 iOS 平台的 SQLite 数据库框架,FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API 即基于 SQLite3 封装的一套 OC 的 API 库。因为 SQLite 的操作都是比较底层的 C 函数用起来比较麻烦,FMDB 对这些底层的东西进行了面向对象的封装,是一个非常好用的第三方库,使用时需要添加苹果提供的依赖库 libsqlite3.dylib。FMDB的gitHub地址
其优点:
1.使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码。
2.对比苹果自带的 CoreData 框架,更加轻量级和灵活。
3.提供了多线程安全的数据库操作方法,有效地防止数据混乱。相关链接:
https://juejin.cn/post/6844903805369188359
https://juejin.cn/post/7075274270472945671
FMDB 的核心 API:
FMDatabase:代表数据库
FMResultSet:代表操作查询结果的游标
FMDatabaseAdditions:对 FMDatabase 类扩展的 Category
FMDatabasePool:数据库连接池支持类
FMDatabaseQueue:数据库多线程操作支持类
FMDB 举例讲解:
2.例子讲解
// 创建数据库
- (void) createDb {
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/test.db"];
NSLog(@"%@", path);
self.db = [[FMDatabase alloc] initWithPath:path];
if([self.db open]) {
[self createTable];
}
else {
NSLog(@"创建数据库失败!");
}
}
// 创建表
- (void) createTable {
NSString *sql = @"create table if not exists tb_user (username varchar(20) primary key, password varchar(20) not null, email varchar(50));";
if([self.db executeUpdate:sql]) {
[self insertData];
}
else {
NSLog(@"创建用户表失败!");
}
}
// 插入数据
- (void) insertData {
NSString *sql = @"insert into tb_user values (?,?,?);";
if([self.db executeUpdate:sql, @"jackfrued", @"123456", @"jackfrued@126.com"]) {
}
else {
NSLog(@"插入数据失败!");
}
}
// 显示表中的记录
- (void) showRecords {
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/test.db"];
self.db = [[FMDatabase alloc] initWithPath:path];
if([self.db open]) {
NSString *sql = @"select * from tb_user";
FMResultSet *rs = [self.db executeQuery:sql];
NSLog(@"%d", rs.next);
while([rs next]) {
NSLog(@"Username: %@", [rs stringForColumn:@"username"]);
NSLog(@"Password: %@", [rs stringForColumn:@"password"]);
NSLog(@"Email: %@", [rs stringForColumn:@"email"]);
}
[self clearData];
}
else {
NSLog(@"创建数据库失败!");
}
}
- (void) clearData {
NSString *sql = @"delete from tb_user";
if([self.db executeUpdate:sql]) {
NSLog(@"清理数据成功!!!");
}
}
- 数据库补充说明
数据库概念 (.db):
数据的集散地,有效地存储和管理数据。App如果有大量的数据需要进行本地存储就可以考虑使用数据库技术,它不仅能够有效的存储和管理数据,更重要的是提供了方便的检索数据的手段。简单的说,数据库是实现数据持久化的重要方案,其他的方案包括:plist文件、archieve归档、NSUserDefaults、普通文件等。数据库类型:
Oracle:甲骨文数据库,当今牛逼关系型数据库
DB2:IBM公司的产品,数据仓库和数据挖掘功能非常强大
SQLServer:微软的产品,适合微软自己的Windows服务器环境
MySQL:开源的数据库产品,中小型站点的数据库服务器首选关系型数据库:比如 SQLite。
数据库数据:若只读取不对其操作则保存在 Nsbundle,若对它要维护修改则放在沙箱中(数据要长久放在 doucument),创建数据库放沙箱的 doucument文件。
关系型数据库特点:
1.用二维表组织数据(行:一条记录;列:一条字段),主键为能够唯一标识一条记录的字段(比如库中已有的学号,再录相同的不能录),添加这个 primary key 表示它是主键。外键为其他表的主键(外来的主键),例如两张表人与身份证例子,pragma foreign_key = on。
2.结构化查询语言(SQL)- SQL分4部分:
DDL:数据定义语言create(创建;可以加前缀命名—>如创建学生表: create Table TBstudent)/drop(删除)/alter(修改)
DML:操作insert(添加)/delete(删除)/update(更新)
DQL:查询select (重点)
DCL:控制grant/revoke1.关系代数
2.集合论
3.实体关系图(ER图),实体间关系:一对一:人与身份证、一对多:部门和员工、多对一:读者与图书
4.事务 ACID,即:Atomic、Consistency、Isolation、Duration;要么全成功,要么全失败;要么全做,要么全不做,以银行转账为例。
5.CoreData
coreData 是苹果官方在 iOS5 之后推出的综合性数据库,其使用了对象关系映射技术,将对象转换成数据,将数据存储在本地的数据库中。coreData 为了提高效率需要将数据存储在不同的数据库中,比如在使用的时候,最好是将本地的数据保存到内存中,这样的目的是访问速度比较快。CoreData 本身并不是一个并发安全的架构,所以在多线程中实现 CoreData 会有问题。
Core Data (ORM:可以将关系模型和对象模型互转),这个苹果原生处理数据库的非常难用,可选用第三方库 ObjectRecord 和 MagicalRecord。各类应用开发中只要牵扯到数据库操作通常都会用到一个概念“对象关系映射(ORM)iOS 中 ORM 框架首选 Core Data,这是官方推荐的不需要借助第三方框架。无论是哪种平台、哪种技术,ORM 框架的作用都是相同的,那就是将关系数据库中的表(准确的说是实体)转换为程序中的对象,其本质还是对数据库的操作(例如 Core Data 中如果存储类型配置为 SQLite 则本质还是操作的 SQLite 数据库)。使用 Core Data 进行数据库存取并不需要手动创建数据库,这个过程完全由 Core Data 框架完成,开发人员面对的是模型,主要的工作就是把模型创建起来,具体数据库如何创建则不用管。
相关链接:
https://juejin.cn/post/7011708888718245925
http://t.zoukankan.com/HJiang-p-7818418.html
6.钥匙串 Keychain
keychain 存储在硬盘上,删除了应用,保存的数据还在。每个 APP 的 keychain 相对来说是独立的,但是也可以实现 APP 之间 keychain 数据的共享,前提是同一个 TeamID 下、且设置了数据共享。
Keychain 是 OS X 和 iOS 都提供一种安全地存储敏感信息的工具,比如,存储用户ID,密码,私钥和证书等。存储这些信息可以免除用户重复输入用户名和密码的过程。Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。简单说来,Keychain 就是一个安全容器。参考 apple 官网介绍
相关链接:
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 相当于第一次重新走逻辑会展示标签。