数据存储
iOS应用数据存储的常用方式
1. Plist存储(属性列表)
- Plist存储(
Documents
)
// 1.Plist存储(生成plist文件)
// 在Plist文件中不能保存自定义对象*+
- (IBAction)saveBtn:(UIButton *)sender {
// 参数:搜索的目录,搜索的范围,是否展开路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSLog(@"%@",path);
// 1.把数组保存到沙盒
NSArray *dataArray = @[@"LN",@10];
NSString *filePath = [path stringByAppendingPathComponent:@"data.plist"];
NSLog(@"%@",filePath);
// 写入沙盒路径
[dataArray writeToFile:filePath atomically:YES];
// 2.把字典保存到沙盒
NSDictionary *dict = @{@"name":@"LN",@"age":@10};
NSString *dictPath = [path stringByAppendingPathComponent:@"dict.plist"];
[dict writeToFile:dictPath atomically:YES];
}
- Plist提取
- 特点: 只能存储系统自带的数据类型, 比如NSDictory, NSArray等等. 自定义的对象无法存储
// 2.Plist提取
- (IBAction)readBtn:(UIButton *)sender {
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [path stringByAppendingPathComponent:@"data.plist"];
NSArray *dataArray = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@"%@",dataArray);
NSString *filePath1 = [path stringByAppendingPathComponent:@"dict.plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath1];
NSLog(@"%@",dict);
}
2. preference(偏好设置)
-
特点: 本质就是一个plist文件; 也是只能存储系统自带的数据类型, 自定义的对象无法存储
-
NSUserDefaults: 用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或开机后这些数据仍然存在。
-
NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前 面的类型(一般转换成NSData),才能用NSUserDefaults存储。
-
偏好设置存储(
Library/Preferences
)
#define LNname @"name"
#define LNage @"age"
// 1.偏好设置存储(key存取,最好定义成宏以防写错)
// 偏好设置不能保存自定义对象*+
- (IBAction)saveBtn:(UIButton *)sender {
// NSUserDefaults保存也是plist文件
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"LN" forKey:LNname];
[defaults setObject:@10 forKey:LNage];
// 写入文件当中
[defaults synchronize];
NSLog(@"%@",NSHomeDirectory());
}
- 偏好设置提取
// 2.偏好设置提取
- (IBAction)readBtn:(UIButton *)sender {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults objectForKey:LNname];
NSInteger age = [defaults integerForKeyLNage];
NSLog(@"%@ %ld",name,age);
}
3. NSKeyedArchiver归档(NSCoding协议)
- 特点: 可以存储自己定义的数据类型, 但是都是一次性的全数据操作
Person
// Person.h
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject<NSCoding>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, strong) Dog *dog;
@end
// Person.m
#import "Person.h"
@implementation Person
// 归档存储会调用(在保存对象时要告诉保存对象那些属性)
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInt:self.age forKey:@"age"];
[aCoder encodeObject:self.dog forKey:@"dog"];
}
// 归档提取(解析文件的时候)会调用,(告诉当前要解析文件当中哪些属性)
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
/*
当只有遵守了NSCoding协议时,才有[super initWithCoder]
@interface Person : NSObject (父类NSObject没有遵守NSCoding协议,继承UIView就可以)
*/
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntForKey:@"age"];
self.dog = [aDecoder decodeObjectForKey:@"dog"];
}
return self;
}
- 归档存储(
tmp
,生成的文件不可以直接打开)
// 归档
// 保存自定义对象(模型类要遵守<NSCoding>协议)*+
- (IBAction)saveBtn:(UIButton *)sender {Person *person = [[Person alloc] init];
person.name = @"LN";
person.age = 10;
Dog *dog = [[Dog alloc] init];
dog.name = @"xiao Hui";
person.dog = dog;
// 获取沙盒目录
NSString *tmpPath = NSTemporaryDirectory();
NSString *filePath = [tmpPath stringByAppendingPathComponent:@"Person.data"];
NSLog(@"%@",NSTemporaryDirectory());
// 归档(archiveRootObject会调用encodeWithCoder:)
[NSKeyedArchiver archiveRootObject:person toFile:filePath];
}
- 归档提取
// 2.归档提取
- (IBAction)readBtn:(UIButton *)sender {
// 获取沙盒目录
NSString *tmpPath = NSTemporaryDirectory();
NSString *filePath = [tmpPath stringByAppendingPathComponent:@"Person.data"];
//归档提取 unarchiveObjectWithFile会调用initWithCoder:
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@--%@",person.name,person.dog.name);
}
-
注意点
initWithCoder
和awakeFromNib
//从xib当加载的时候 (是加载完毕时调用)
-(void)awakeFromNib{
NSLog(@"awakeFromNib==%@",self.btn);
// awakeFromNib==<UIButton: 0x7ffd08c1e450; frame = (100 153; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffd08c1af60>>
}
//当解析一个文件的时候会调用initWithCoder (一开始加载Xib就调用)
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if(self = [super initWithCoder:aDecoder]){
NSLog(@"initWithCoder==%@",self.btn);
// initWithCoder==(null)
}
return self;
}
4. SQLite3
-
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
-
数据库可以分为2大种类
- 关系型数据库(主流):如,MySQL
- 嵌入式/移动客户端 :如,SQLite 对象型数据库
-
特点: 存储一些大批量的数据, 排序, 统计等操作
什么是SQLite
- SQLite是一款轻型的嵌入式数据库
- 它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了
- 它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快
如何存储数据到数据库
- 数据库的存储结构和excel很像,以表(table)为单位
数据库存储数据的步骤
- 新建数据库文件
- 新建一张表(table)
- 添加多个字段(column,列,属性)
- 添加多行记录(row,每行存放多个字段对应的值)
Navicat
是一套适用于MySQL, SQLite等多个数据库系统地图形化数据库管理、报告以及监控的工具。具有高性能的、商业智能的、强大的备份功能
理论基础:
-
表格组成: 行(记录)和列(属性)
-
"属性" 是用来标识这一列应该存放什么
-
"记录" 是用来存放一条数据
-
属性类型
-
blob :二进制类型
-
integer : 整型
-
real : 浮点型
-
text :文本类型
null : 空
-
主键
-
主键(Primary Key,简称PK)用来唯一地标识某一条记录
例如t_student可以增加一个id字段作为主键,相当于人的身份证
-
主键可以是一个字段或多个字段
例如: 行和列
-
主键的设计原则
- 主键应当是对用户没有意义的
- 永远也不要更新主键
- 主键不应包含动态变化的数据
- 主键应当由计算机自动生成
SQL语言简介
- 特点:不区分大小写(比如数据库认为user和UsEr是一样的)
- SQL中的常用关键字(注意:数据库中不可以使用关键字来命名表、字段)
select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index等等
- SQL语句的种类
- 数据定义语句(DDL:Data Definition Language)
包括create和drop, Alert等操作,在数据库中创建新表或删除表(create table或 drop table)
- 数据操作语句(DML:Data Manipulation Language)
包括insert、delete、update等操作,上面的3种操作分别用于添加、修改、删除表中的数据
- 数据查询语句(DQL:Data Query Language)
可以用于查询获得表中的数据,关键字select是DQL(也是所有SQL)用得最多的操作,其他DQL常用的关键字有where,order by,group by和having
DDL语句
- 创表
格式
create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
示例
create table t_student (id integer, name text, age inetger, score real) ;
经验
实际上SQLite是无类型的
就算声明为integer类型,还是能存储字符串文本(主键除外)
建表时声明啥类型或者不声明类型都可以,也就意味着创表语句可以这么写:
create table t_student(name, age);
为了保持良好的编程规范、方便程序员之间的交流,编写建表语句的时候最好加上每个字段的具体类型
语句优化
创建表格时, 最好加个表格是否已经存在的判断, 这个防止语句多次执行时发生错误
create table if not exists 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
- 删表
格式
drop table 表名 ;
drop table if exists 表名 ;
示例
drop table t_student ;
语句优化
删除表格时, 最好加个表格是否已经存在的判断, 这个防止语句多次执行时发生错误
drop table if exists 表名 ;
- 修改表
注意: sqlite里面只能实现Alter Table的部分功能
不能删除一列, 修改一个已经存在的列名
修改表名
ALTER TABLE 旧表名 RENAME TO 新表名
新增属性
ALTER TABLE 表名 ADD COLUMN 列名 数据类型 限定符
-
约束
- 简单约束
不能为空,not null :规定字段的值不能为null
不能重复,unique :规定字段的值必须唯一
默认值,default :指定字段的默认值
示例
`create table t_student (id integer, name text not null unique, age integer not null default 1) ;`
name字段不能为null,并且唯一
age字段不能为null,并且默认为1
- 主键约束
添加主键约束的原因?
如果t_student表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据,良好的数据库编程规范应该要保证每条记录的唯一性,为此,增加了主键约束,也就是说,每张表都必须有一个主键,用来标识记录的唯一性
- 主键的声明?
在创表的时候用primary key声明一个主键
create table t_student (id integer primary key, name text, age integer) ;
integer类型的id作为t_student表的主键
- 主键字段
只要声明为primary key,就说明是一个主键字段
主键字段默认就包含了not null 和 unique 两个约束
如果想要让主键自动增长(必须是integer类型),应该增加autoincrement
create table t_student (id integer primary key autoincrement, name text, age integer) ;
DML语句
- 插入数据(insert)
格式
insert into 表名 (字段1, 字段2, …) values (字段1的值, 字段2的值, …) ;
示例
insert into t_student (name, age) values (‘sz’, 10) ;
注意
数据库中的字符串内容应该用单引号 ’ 括住
- 更新数据(update)
格式
update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, … ;
示例
update t_student set name = ‘wex’, age = 20 ;
注意
上面的示例会将t_student表中所有记录的name都改为wex,age都改为20
- 删除数据(delete)
格式
delete from 表名 ;
示例
delete from t_student ;
注意
上面的示例会将t_student表中所有记录都删掉
- 条件语句
作用
如果只想更新或者删除某些固定的记录,那就必须在DML语句后加上一些条件
条件语句的常见格式
where 字段 = 某个值 ; // 不能用两个 =
where 字段 is 某个值 ; // is 相当于 =
where 字段 != 某个值 ;
where 字段 is not 某个值 ; // is not 相当于 !=
where 字段 > 某个值 ;
where 字段1 = 某个值 and 字段2 > 某个值 ; // and相当于C语言中的 &&
where 字段1 = 某个值 or 字段2 = 某个值 ; // or 相当于C语言中的 ||
-
条件语句练习
示例
将t_student表中年龄大于10 并且 姓名不等于wex的记录,年龄都改为 5
删除t_student表中年龄小于等于10 或者 年龄大于30的记录
猜猜下面语句的作用
update t_student set score = age where name = ‘wex’ ;
DQL
格式
select 字段1, 字段2, … from 表名 ;
select * from 表名; // 查询所有的字段
示例
select name, age from t_student ;
select * from t_student ;
select * from t_student where age > 10 ; // 条件查询
查询相关语句
统计
count(X)
select count(*) from t_student
select count(age) from t_student
avg(X)
计算某个字段的平均值
sum(X)
计算某个字段的总和
max(X)
计算某个字段的最大值
min(X)
计算某个字段的最小值
排序
查询出来的结果可以用order by进行排序
select 字段1, 字段2 from 表名 order by 字段 ;
select * from t_student order by age ;
默认是按照升序排序(由小到大),也可以变为降序(由大到小)
select * from t_student order by age desc ; //降序
select * from t_student order by age asc ; // 升序(默认)
也可以用多个字段进行排序
select * from t_student order by age asc, height desc ;
先按照年龄排序(升序),年龄相等就按照身高排序(降序)
limit分页
使用limit可以精确地控制查询结果的数量,比如每次只查询10条数据
格式
select * from 表名 limit 数值1, 数值2 ;
示例
select * from t_student limit 4, 8 ;
可以理解为:跳过最前面4条语句,然后取8条记录
分页
limit常用来做分页查询,比如每页固定显示5条数据,那么应该这样取数据
第1页:limit 0, 5
第2页:limit 5, 5
第3页:limit 10, 5
第n页:limit 5*(n-1), 5
特殊案例
select * from t_student limit 7 ;
相当于select * from t_student limit 0, 7 ;
表示取最前面的7条记录
多表查询
select 字段1, 字段2, … from 表名1, 表名2 ;
别名
select
别名1.字段1 as 字段别名1,
别名2.字段2 as 字段别名2,
…
from
表名1 as 别名1,
表名2 as 别名2 ;
可以给表或者字段单独起别名
as 可以省略
表连接查询
select 字段1, 字段2, … from 表名1, 表名2 where 表名1.id = 表名2.id;
外键
如果表A的主关键字是表B中的字段,则该字段称为表B的外键
保持数据一致性,完整性,主要目的是控制存储在外键表中的数据。 使两张表形成关联,外键只能引用外表中的列的值或使用空值。
12-(掌握)代码实现SQLite-DDL
1. 创建一个Swift项目
2. 导入系统框架sqlite3.tbd(sqlite3.dylib)
3. 建立桥接文件, 导入头文件sqlite3.h
1. 新建一个.h 头文件
2. 设置为桥接文件
代码实现
1. 打开数据库
2. 使用打开的数据库, 执行DDL语句, 创建一个数据库表
3. 使用打开的数据库, 执行DDL语句, 创建一个数据库表
3. 将数据库操作封装成一个工具类
13-(掌握)代码实现DML语句-Insert
1. 创建一个Student类
属性
name
age
构造方法
init(name: String, age: Int)
2. 创建数据库操作方法
数据库中, 对Student对象的操作封装
insertStudent()
14-(了解)代码实现DML语句-Insert绑定参数
准备语句(prepared statement)对象
准备语句(prepared statement)对象一个代表一个简单SQL语句对象的实例,这个对象通常被称为“准备语句”或者“编译好的SQL语句”或者就直接称为“语句”。
操作历程
1. 使用sqlite3_prepare_v2或相关的函数创建这个对象
如果执行成功,则返回SQLITE_OK,否则返回一个错误码
2. 使用sqlite3_bind_*()给宿主参数(host parameters)绑定值
sqlite3_bind_text
参数1:
准备语句
参数2:
绑定的参数索引 (从1开始)
参数3:
绑定的参数内容
参数4:
绑定的参数长度 (-1代表自动计算长度)
参数5:
参数的处理方式
SQLITE_TRANSIENT 会对字符串做一个 copy,SQLite 选择合适的机会释放
SQLITE_STATIC / nil 把它当做全局静态变量, 不会字符串做任何处理,如果字符串被释放,保存到数据库的内容可能不正确!
注意: swift中没有宏的概念
// 替换 sqlite3.h 中的宏
private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
3. 通过调用sqlite3_step() 一次或多次来执行这个sql
对于DML语句, 如果执行成功, 返回SQLITE_DONE
对于DQL语句, 通过多次执行获取结果集, 继续执行的条件是返回值 SQLITE_ROW
4. 使用sqlite3_reset()重置这个语句,然后回到第2步,这个过程做0次或多次
5. 使用sqlite3_finalize()销毁这个对象, 防止内存泄露
DML语句-Insert插入数据优化
只要在执行多个SQL语句之前, 手动开启事务, 在执行完毕之后, 手动提交事务, 这样 再调用SQL方法执行语句时, 就不会再自动开启和提交事务
事务
5. Core Data
- 特点: 对SQLite3的一层面向对象的包装, 本质还是要转换成为对应的SQL语句去执行
钥匙串
- APP之间数据共享
- 系统级别的加密, 安全性高
- 当APP 被删除时, 存储的数据依然存在