[TOC]
一、应用的文件结构
I. 应用沙盒
- 每个iOS应用都有一个 应用沙盒「文件系统目录」,与其他文件系统隔离
- 应用必须在自己的沙盒里,其他应用不能访问他人的沙盒
II. 应用沙盒的结构
1. 应用程序包
包含所有 资源文件 和 可执行文件
其中的文件夹名是乱码,是为了 防止重名采取加密
// 获取应用沙盒「应用文佳夹」的文件路径
NSString *homePath = NSHomeDirectory();
2. tmp
- 保存运行时生成的 临时数据,使用完毕后会将相应的文件删除
- 应用没有运行时,系统可能会清除该目录下文件
- iTunes 同步时,不会备份
// 获取 tmp 文件路径
NSString *tmpPath = NSTemporaryDirectory();
3. Documents
- 保存运行时生成的 需要持久化的数据
- iTunes 同步时,会备份该目录
- 例:游戏应用的 存档保存在这里
// 获取 Documents 文件路径
// 方法一、利用沙盒根目录拼接 ”Documents” 字符串
// 不建议采用,因为新版本的操作系统可能会修改目录名
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
// 方法二、利用 NSSearchPathForDirectoriesInDomains 函数
/**
* @param NSDocumentDirectory:搜索目录是,Documents 目录
* @param NSUserDomainMak:搜索范围是,用户文件夹
* @param NO:不展开全路径:~/Library/Caches
* @return NSArray*
*/
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString *documents = [array objectAtIndex:0];
4. Library/Caches
- 保存运行时生成的需要持久化的文件
- 一般保存 存储体积大,不需要备份的非重要数据
- iTunes 同步时,不会备份
// 获取 Caches 文件路径
// 方法和 获取 Document 文件路径 方法一致
// NSCachesDirectory:搜索目录是 caches 目录
NSArray* array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, NO);
NSString *caches = [array objectAtIndex:0];
5. Library/Preference
- 保存应用所有的偏好设置
- iOS系统的设置应用 会在
Preference
目录下查找应用的设置信息 - 每个应用都有个
NSUserDefaults
实例,通过它来存取偏好设置 - 比如,保存用户名、字体大小、是否自动登录
- iTunes 同步时,会备份
// 获取 Preference 文件路径
// 通过 NSUserDefaults类 存取该目录下的 设置信息,这里以用户标准设置信息为例
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
二、iOS应用数据存储常用方式
1. Perference「偏好设置」
简介
- 存储位置
Library/Preference
- 不需要获取文件夹的全路径
- 快速的做键值对存储
- Perference 不能存储自定义对象
示例
- 存 偏好设置文件
// 拿到 standardUserDefaults方法的单例对象
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"Data" forKey:@"Key"];
[userDefaults setBool:YES forKey:@"Key2"];
// iOS7 之前,以上内容存储在缓存「定时存储」中,现在会自动同步到硬盘中
// iOS7 之前,不会立刻和硬盘同步,要添加一下同步代码「防止调用了set方法后数据还没写入磁盘,应用程序就终止了」
[userDefaults synchronize];
- 取 偏好设置文件
NSString *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"Key"];
2. XML「属性列表」
简介
- 存储位置
Library/Caches
- 一般存储 Array 和 Dictionary 集合,后缀名为
.plist
- 能不能使用 Plist,看有没有 writeToFile
- XML 不能存储自定义对象
示例
- 存储 plist 文件
// 0. 数据源 数组集合
NSArray *arr = @[@"123",@96];
// 1. 获取存放路径
NSString *cachePath = NSSearchPathForDirectoriesInDomains (NSCachesDirectory,NSUserDomainMask,YES)[0];
// 2. 拼接完整路径「全路径」
NSString *filePath = [cachePath stringByAppendingPathComponent:@"arr.plist"];
// 3. 存储「File: 文件全路径」
[arr writeToFile:filePath atomically:YES];
- 读取 plist 文件
// 1. 获取存放路径
NSString *cachePath = NSSearchPathForDirectoriesInDomains (NSCachesDirectory,NSUserDomainMak,YES)[0];
// 2. 拼接完整路径「全路径」
NSString *filePath = [cachePath stringByAppendingPathComponent:@"arr.plist"];
// 3. 取出文件「由于之前存的是数组集合,所以取的时候也是数组集合」
NSArray *arr = [NSArray arrayWithContentOfFile:filePath];
3. NSKeyedArchiver「归档存储」
简介
- 存储位置
Library/Caches
- 将 一个对象 写入 一个文件,
后缀名随便
- 自定义对象要归档,自定义对象 必须遵守
NSCoding
协议,并实现协议方法
示例
- 归档对象的定义
@implementation Person
// 存
// 调用:自定义对象 归档的时候 调用
// 作用:说明自定义对象有哪些属性需要 归档
- (void)encoderWithCoder:(NSCoder *)aCoder{
[super encoderWithCoder:aCoder]; // 如果 父类 使用了 encoderWithCoder 方法的话
[aCoder encoderObject:_name forKey:@"name"];
[aCoder encoderInt:_age forKey:@"age"];
}
// 取
// 调用:自定义对象解档的时候调用
// 作用:解析文件
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
// 只要父类遵守了 NSCoding 协议,就调用 父类的 initWithCoder,先初始化父类 self = [super initWithCoder: aDecoder];
// 这里 super == NSObject,NSObject 并没有遵守 NSCoding协议
// 具体 initWithFrame 和 initWithCoder 的区别,见 01 UI上
if (self = [super init]){
// 注意:一定要给成员变量赋值
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
@end
- 归档步骤「存」
// 自定义对象
Person *person = [[Persong alloc] init];
// 1. 获取存放路径
NSString *cachePath = NSSearchPathForDirectoriesInDomains (NSCachesDirectory,NSUserDomainMak,YES)[0];
// 2. 拼接完整路径「全路径」
NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"]; // 后缀名随便取名字
// 3. 把自定义对象 归档
[NSKeyedArchiver archiveRootObject:person toFile:filePath];
- 解档步骤「取」
// 1. 获取存放路径
NSString *cachePath = NSSearchPathForDirectoriesInDomains (NSCachesDirectory,NSUserDomainMak,YES)[0];
// 2. 拼接完整路径「全路径」
NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"];
// 3. 把自定义对象 解档
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
4. NSData
简介
- 将 多个对象 写入 同一个文件
- 为一些数据提供临时存储空间,以便随后写入文件
- 存放从磁盘读取的文件内容
- 使用
[NSMutableData data]
创建 可变数据空间
示例
- 归档,编码步骤「存」
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个 NSKeyedArchiver 对象
NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
// 开始存档对象,存档的数据都会存储到 NSMutableData 中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存档完毕「一定要调用这个方法」
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];
- 解档,解码步骤「取」
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢复完毕
[unarchiver finishDecoding];
- 归档实现深复制
// 临时存储person1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一个新的Person对象
Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"person1:0x%x", person1);
NSLog(@"person2:0x%x", person2);
5. SQLite3「数据库文件」
- 存储在
Documents 文件夹
- 后缀名为
.db
I. 数据库简介
定义:按照数据结构来组织、存储和管理的仓库
分类
- 关系型
PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase
嵌入式/移动客户端:SQLite - 对象型
数据类型: | blob | real | integer | text | null |
---|---|---|---|---|---|
解释: | 二进制 | 浮点数/实数 | 整数 | 文本字符串 | 空 |
支持的数据类型:数据库本质不区分类型,只是方便程序员沟通
数据类型: | blob | real | integer | text | null |
---|---|---|---|---|---|
解释: | 二进制 | 浮点数/实数 | 整数 | 文本字符串 | 空 |
0)数据库的结构
表「tables」:可以有多个
- 字段「column」列,属性
- 记录「row」每行对应的 多个字段的值
1)主键约束「Primary Key,简称PK」
定义
- 用来唯一地标识某一条记录
- 可以是 一或多 个字段
主键的设计原则
- 主键应当是对用户没有意义的
- 不要更新主键
- 主键不应包含动态变化的数据
- 主键应当由计算机自动生成
2)外键约束「Foreign Key,简称FK」
- 用来建立表与表之间的联系
- A表的外键字段 为 B表的主键字段
II. SQL 语句的分类
简介:SQL「structured query language」结构化查询语言,不区分大小写
规范:SQL 关键字 大写
1)数据定义语句「DDL:Data Definition Language」
- 包括 create 和 drop 等操作
/*只有 多行注释
一般格式,如果表存在,重复执行会报错*/
create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …);
/*如果不存在,就创建表;如果存在,则 不创建
防止重复创建表,覆盖原来表的数据*/
create table if not exists 表名 (id integer, name text, primary key(id)); /*这里的 id 被设为主键,但无法自增长*/
/*删除表,如果表不存在,重复执行会报错*/
drop table 表名;
/*删除表 完整的写法*/
drop table if exists 表名;
- 简单约束「建表 时添加」
建议:尽量给字段设定严格的约束,以保证数据的规范性
/*id 设置为主键,且设定 id 自增长「自增长的 id类型必须是 integer」*/
create table if not exists 表名 (id integer primary key autoincrement, name text);
create table T_student (
id integer primary key autoincrement, /*主键默认有 not null unique 约束,*/
name text not null unique, /*name字段 不能为null,且 唯一*/
age integer not null default 1 /*age字段 不能为null,且 默认为1*/
);
/* t_student 表中 有一个叫做 fk_t_student_class_id_t_class_id 的外键
这个外键的作用: 用 t_student 表中的 class_id字段 引用 t_class表的id字段 */
CREATE TABLE IF NOT EXISTS t_student (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER,
class_id INTEGER,
CONSTRAINT fk_t_student_class_id_t_class_id FOREIGN KEY (class_id) REFERENCES t_class (id)
);
2)数据操作语句「DML:Data Manipulation Language」
- 包括insert、update、delete等操作
/*插入数据
注:数据库 字符串内容用 ' 括起来「虽然 " 不会报错」
重复插入不会报错,会插入多个相同的数据*/
insert into 表名(字段1, 字段2, ...) values (字段1的值, 字段2的值, ...);
/*更新数据
没有说明具体哪条记录,所有字段1、2的值都会更新*/
update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值;
/*删除数据
没有说明具体那条记录,会删除表中所有的记录*/
delete from 表名;
- 条件语句
where 字段 = 某个值; /*判断是否为 某个数,= 是等于*/
where 字段 is 某个值; /*判断是否为 某个数,is 是等于*/
where 字段 != 某个值; /*判断是否不为 某个数*/
where 字段 is not 某个值; /*判断是否不为 某个数,is not 是不等于*/
where 字段 > 某个值;
where 字段1 = 某个值 and 字段2 > 某个值; /* and相当于C语言中的 && */
where 字段1 = 某个值 or 字段2 = 某个值; /* or 相当于C语言中的 || */
- 根据条件语句使用DML
/*格式*/
update 表名 set 字段1=属性1, 字段2=属性2 where 依据字段1=依据字段1的值;
/*示例*/
/*1. 删除t_student表中年龄小于等于10 或者 年龄大于30的记录*/
delete from t_student where age <= 10 or age > 30 ;
/*2. 将t_student表中名字等于jack的记录,score字段的值 都改为 age字段的值*/
update t_student set score = age where name = ‘jack’ ;
3)数据查询语句「DQL:Data Query Language」
- 可以用于查询获得表中的数据「查询操作使用最频繁」
- 其他DQL常用的关键字有where,order by,group by和having
/*格式*/
select 字段1, 字段2, … from 表名;
select * from 表名; /*查询所有的字段*/
/*示例,查询所有 age>10 的记录*/
select * from T_tableName where age>10;
- 起别名「字段和表 都可以起别名」
作用:当查询多张表的时候,多张表中有同名的字段,可以通过起别名来区分
/*格式, 下面3行代码作用相同,格式不同*/
select 字段1 别名, 字段2 别名, … from 表名 别名;
select 字段1 as 别名, 字段2 as 别名, … from 表名 as 别名;
select 别名.字段1, 别名.字段2, … from 表名 别名;
/*示例*/
/*name起别名为ThisName, age起别名为ThisAge*/
select name ThisName, age ThisAge from T_student;
/*T_student表起个别名叫做s,利用s来引用表中的字段*/
select s.name, s.age, t.name, t.age from T_student s, T_teacher t;
- 相关函数的使用
select count (字段) from 表名; /*count() 查询指定内容有多少条记录*/
select * from 表名 order by 字段1; /*根据 字段1将查出的记录排序,默认升序「由小到大」*/
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」
/* 格式1:limit 数值1, 数值2
数值1:跳过几条记录
数值2:读取几条记录 */
select * from T_student limit 4, 8; /*跳过最前面4条语句,取8条记录*/
/*格式2:limit 取前几条数据的个数*/
select * from T_student limit 7; /* 取最前面的7条记录,相当于 select * from T_student limit 0, 7; */
- 表连接查询
内连接:inner join 或者 join 「显示的是左右表都有完整字段值的记录」
左外连接:left outer join「保证左表数据的完整性」
- 表连接查询实践
问:有 T_student 表和 T_class 表,其中 T_student 表的 class_id 为外键 指向 T_class 的 id,查 T_student 表中 iOSClass 班的所有学生
/* 普通查询方式 */
select s.name,s.age from T_student s, T_class c where s.class_id = c.id and c.name = 'iOSClass';
/* 内连接查询方式,这里的 on 相当与 where,inner join 和 ,效果一样*/
select s.name,s.age from T_student s inner join T_class c on s.class_id = c.id;
/* 不使用连接的查询方式,嵌套查询 */
select s.name from T_student s where s.class_id = (select c.id from T_class c where c.name = 'iOSClass');
4)模糊查询
模糊查询 like 的使用格式
SELECT 字段 FROM 表名 WHERE 某字段 LIKE 条件;
通配符
-
%
:代替 任意0个或多个 字符
- 可匹配任意类型和长度的字符
- 有些情况下若是中文,使用两个百分号
%%
表示。 -
%三%
:可以搜出所有含有 三 的记录 -
%三%猫
:能够搜出「三猫」,却搜不出「猫三」 - 要搜出即含有「猫」,又含有「三」的所遇记录使用
SELECT 字段1 FROM 表名 WHERE 字段2 LIKE '%三%' AND 字段2 LIKE '%猫%';
-
_
:代替 任意单个 字符
- 常用来限制表达式的字符长度语句
-
_三_
:只能找出是 三个字 且 中间是「三」的记录 -
三__
:只能找出是 三个字 且 首字是「三」的记录
-
[]
:代替括号内所列字符中的一个「类似正则表达式」
- 指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个
-
[张李王]三
:将找出「张三」、「李三」、「王三」而不是「张李王三」
-
[^]
:代替不在括号所列之内的单个字符
- 要求所匹配对象为指定字符以外的 任一一个 字符。
-
[^张李王]三
:找出不姓「张」、「李」、「王」的「赵三」、「孙三」等
- 查询内容包含通配符时
- 查询特殊字符
%
、_
、[
- 把特殊字符用
[]
括起便可正常查询 - 例:
like '5[%]'
表示查询含有5%
的字段
- 通配符的综合使用
-
LIKE 'Mc%'
:搜索以字母 Mc 开头的所有字符串(如 McBadden) -
LIKE '%inger'
:搜索以字母 inger 结尾的所有字符串(如 Ringer、Stringer) -
LIKE '%en%'
:搜索在任何位置包含字母 en 的所有字符串(如 Bennet、Green、McBadden) -
LIKE '_heryl'
:搜索以字母 heryl 结尾的所有 六个字母 的名称(如 Cheryl、Sheryl) -
LIKE '[CK]ars[eo]n'
:搜索下列字符串:Carsen、Karsen、Carson 和 Karson(如 Carson) -
LIKE '[M-Z]inger'
:搜索以字符串 inger 结尾、以从 M 到 Z 的任何单个字母开头的所有名称(如 Ringer) -
LIKE 'M[^c]%'
:搜索以字母 M 开头,并且第2个字母不是 c 的所有名称(如 MacFeather)
5)事务操作
事务
- 事务可以使多个操作不受多线程的影响,同时执行
- 如果同一个事务中的一个操作失败,事务就会回滚
- 一旦事务回滚,事务内的所有操作便会无效
注意:
- 事务的使用很耗费系统性能,尽量少用
- 事务开启:对数据库的备份
- 回滚:将备份还原为原来的数据
- 提交:对数据库的部分更新,删除备份
示例
// 1. 开启事务
begin transaction;
// 2. 数据库其他操作
// 3. 回滚事务「主动的操作」
rollback transaction;
// 4. 提交事务
commit transaction;
III. SQLite
简介
- SQLite 是轻型的「客户端专用的」嵌入式数据库,跨平台
- 适用于存储大批量数据
- 占用资源低,在嵌入式设备中可能只需 几百KB的内存
- 处理速度比 MySQL、PostgreSQL 数据库都要快
注意
使用前先添加
sqlite3.framework
,并 导入主头文件#import
模型对象数据要存储到 数据库中 先用 NSData 转为 二进制数据
1)创建或打开数据库
-
sqlite3_open()
将根据文件路径打开数据库 - 如果数据库不存在,则会创建一个新的
// path:数据库文件的存放路径「必须是 C 语言字符串(而非 NSString )」
sqlite3 *db = NULL; // 指针 db 指向一个打开的数据库实例对象
// 如果 result 等于常量 SQLITE_OK,则表示成功打开数据库
//「注:result 是 int 类型,这里有许多 int 类型的数字常量标记状态,在这里写程序时应避免」
int result = sqlite3_open([path UTF8String], &db);
sqlite3_close(db); // 关闭数据库「有开就有关」
2)不返回数据的 sqlite3_exec() 函数
-
sqlite3_exec()
可以执行任何SQL语句「比如创表、更新、插入和删除操作」 - 一般不用它执行查询语句,因为它不会返回查询到的数据
示例:执行创表语句
// 凡是传地址的指针提前置为 NULL 防止野指针出现
char *errorMsg = NULL; // errorMsg:存储错误信息
// sql:想要执行的 SQL 语句「C 语言字符串」
char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);";
// result 返回执行是否成功的状态
int result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);
SQlite 的事务处理语句
- 开启事务:
begin transaction;
- 回滚事务:
rollback;
- 提交事务:
commit;
3)预编译绑定
优点
- 防止 SQL 注入
- 提高性能
// ? 是占位符,在占位符号替换真实数据时会进行安全检测,防止 SQL 注入
char *sql = "insert into t_person(name, age) values(?, ?);";
sqlite3_stmt *stmt; // 存放结果集
// sqlite3_prepare_v2()返回值 == SQLITE_OK,说明SQL语句已经准备成功,没有语法问题
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
/* sqlite3_bind_text():大部分绑定函数都只有3个参数
参数1:sqlite3_stmt *类型
参数2:占位符的位置,第一个占位符的位置是 1,不是 0
参数3:占位符要绑定的值
参数4:在参数3中所传递数据的长度,对于 C 字符串,可以传递 -1 代替字符串的长度
参数5:一个可选的函数回调,一般用于在语句执行后完成内存清理工作 */
sqlite3_bind_text(stmt, 1, "母鸡", -1, NULL); // 将真实值绑定所选的位置上,防止SQL注入
sqlite3_bind_int(stmt, 2, 27);
}
// sqlite3_step():执行SQL语句,返回 SQLITE_DONE 代表 成功执行完毕
if (sqlite3_step(stmt) != SQLITE_DONE) {
NSLog(@"插入数据错误");
}
// sqlite3_finalize():销毁sqlite3_stmt *对象
sqlite3_finalize(stmt);
4)查询数据
sqlite3_stmt *stmt;
char *sql = "select id,name,age from t_person;";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) { // sqlite3_step():返回SQLITE_ROW代表遍历到一条新记录
int _id = sqlite3_column_int(stmt, 0); // sqlite3_column_*():获取每个字段对应的值
int _age = sqlite3_column_int(stmt, 2); // 参数2对应上面 sql 字段的索引「从0开始」
char *_name = (char *)sqlite3_column_text(stmt, 1);
NSString *name = [NSString stringWithUTF8String:_name];
}
}
sqlite3_finalize(stmt);
IV. FMDB 框架
简介
- FMDB 是 iOS 平台的 SQLite 数据库框架,相比苹果自带的 Core Data 框架,更加轻量级和灵活
- FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API
- 提供了多线程安全的数据库操作方法
核心类简介
-
FMDatabase
:一个该对象代表一个单独的SQLite数据库,用来执行SQL语句 -
FMResultSet
:使用 FMDatabase 执行查询后的结果集 -
FMDatabaseQueue
:用于在多线程中执行多个查询或更新,它是线程安全的
1)打开数据库
通过指定 SQLite 数据库文件路径来创建 FMDatabase 对象
FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
NSLog(@"数据库打开失败!");
}
以上代码的文件路径 path
有三种情况
- 具体文件路径
如果具体文件夹不存在,会自动创建 -
@""
空字符串
会在临时目录「tmp」创建一个空的数据库
当 FMDatabase 连接关闭时,数据库文件也被删除 -
nil
空
会创建一个内存中临时数据库,当 FMDatabase 连接关闭时,数据库会被销毁
2)更新数据库
在 FMDB 中,除查询以外 的所有操作「create、drop、insert、update、delete等」,都称为“更新”
// 使用executeUpdate:方法执行更新,返回的 BOOL 值表示是否执行成功
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
示例
// 1. 从 doc 里获得文件路径
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];
// 2. 创建数据库
FMDatabase *db = [FMDatabase databaseWithPath:path];
// 3. 打开数据库
if (![db open]) {
NSLog(@"数据库打开失败!");
}
// 4. 在数据库中创建表
[db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer);"];
// 5. 数据库内容更新
[db executeUpdate:@"insert into t_student (name, age) values (?, ?);", name, age];
[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"];
// 6. 使用完后关闭数据库
[db close];
3)查询数据库
查询方法
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
示例
// 查询数据
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
// 遍历结果集
while ([rs next]) {
NSString *name = [rs stringForColumn:@"name"];
int age = [rs intForColumn:@"age"];
double score = [rs doubleForColumn:@"score"];
}
4)事务处理
- 开启事务:
[db beginTransaction]
- 回滚事务:
[db rollback]
- 提交事务:
[db commit]
5)FMDatabaseQueue
- FMDatabase 这个类是线程不安全的,如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题
- 为了保证线程安全,FMDB 提供方便快捷的 FMDatabaseQueue 类
FMDatabaseQueue 的创建
// 1. 从 doc 里获得文件路径
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];
// 2. 创建数据库队列
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
FMDatabaseQueue 的数据库安全调用
- 对象方法
inDatabase:
给的是一个 已经打开的数据库
[queue inDatabase:^(FMDatabase *db) {
// 传入的只是一个打开的数据库,仍需要创表
[db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer);"];
// 更新数据库的其他操作
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
}];
// 事务使用
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
// 事务回滚
*rollback = YES;
}];
6. Core Data 框架
简介
- Core Data 是苹果自带的一种持久化技术,能将模型对象的状态持久化到磁盘
提供对象-关系映射「Object-relationalmaping(O/RM)」的功能,即能够将 OC对象 转化成 数据,保存在 SQLite3 数据库文件中
也能够将保存在数据库中的数据 还原成 OC对象
- Core Data 是一种模型层的技术,可以建立代表程序状态的模型层
- Core Data 是完全独立于任何 UI层级的框架,它是作为模型层框架被设计出来的
- 使用前先
添加CoreData.framework
和 导入主头文件
特点
- Core Data 不仅是一个加载、保存数据的框架,还能和内存中的数据很好的共享
- 以面向对象的方式存储和管理数据
- 数据操作中无需编写任何 SQL 语句
- 缺点 重量级框架,侵入性强「一般不建议使用」
I. 模型文件「Data Model」
简介
- 是数据库的图形化模型,文件名通常会和应用名相同
- 来描述应用的所有 实体 和 实体属性
映射关系
- 实体「Entity」:映射到 NSManageObject 类上,跟数据库进行映射的对象
- 属性「Attribute」:映射到 NSManageObject 类 的 property 上
- 关联「Relationship」:映射到 NSManageObject 类的 property 上,指向数据库中的其他对象
II. NSManagedObject
- 通过 Core Data 从数据库取出的对象,默认情况下都是 NSManagedObject 对象
- NSManagedObject 的工作模式有点类似于 NSDictionary 对象,通过 键值对 来存取所有的实体属性
- 存储属性值:setValue:@"属性值" forKey: @"属性名"
获取属性值:valueForKey: @"属性名"
III. 主要对象
NSManagedObjectContext
:被管理的上下文,负责应用和数据库之间的交互「CRUD」
对象中有 持久化协调器属性
persistentStoreCoordinator
NSManagedObjectModel
:被管理的对象模型,对应定义的模型文件对象中有 实体属性
entities
NSEntityDescription
:实体描述,对象中有 属性name
NSPersistentStoreCoordinator
:持久化存储协调器,数据存储的文件和程序之间联系的桥梁对象中有 被管理对象模型属性
managedObjectModel
负责管理不同对象的上下文
添加持久化存储库「如 SQLite 数据库」
Core Data可以将多个存储附属于同一个持久化存储协调器,并且除了SQL可选择的类型外,还有很多存储类型可供选择
IV. 开发步骤
0)创建模型文件
-
例子 模型文件
-
选择模板
-
添加实体
- 添加 Person 属性
-
添加 Card 属性
- 建立 Person 和 Card 的关系
-
Person 中 添加 Card 属性
-
Card 中添加 Person 属性
- 下图中的表示 Card 中有个 Person 类型的 person属性
目的就是建立 Card 跟 Person 之间的一对一关联关系「建议补上这一项」
在 Person 中加上 Inverse 属性后,会发现 Card 中 Inverse 属性也自动补上了
-
1)搭建 Core Data 上下文环境
// 1. 从应用程序包中加载模型文件,读取 app 中的所有实体信息
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
// 2. 传入模型,初始化 NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model] autorelease];
// 3. 构建 SQLite 文件路径
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data"]];
// 4. 添加持久化存储库,这里使用SQLite作为存储库
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
if (store == nil) { // 直接抛异常
[NSException raise:@"添加数据库错误" format:@"%@", [error localizedDescription]];
}
// 5. 初始化 NSManagedObjectContext 上下文,设置 persistentStoreCoordinator 属性
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = psc;
// 6. 上下文不用后,要及时释放掉
[context release];
2)添加数据
// 1. 传入上下文,创建一个Person实体对象
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
// 2. 设置Person的简单属性
[person setValue:@"SunFarrell" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
// 传入上下文,创建一个Card实体对象
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"2332332333" forKey:@"no"];
// 设置Person和Card之间的关联关系
[person setValue:card forKey:@"card"];
// 3. 利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [context save:&error];
if (!success) {
[NSException raise:@"访问数据库错误" format:@"%@", [error localizedDescription]];
}
// 4. 更新操作「耗时间」:在更改了实体对象的属性后,将更改的数据同步到数据库
[context save:&error];
3)查询数据
// 1. 初始化一个查询请求
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
// 2. 设置要查询的实体
request.entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
// 设置排序(按照age降序)
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
// 设置条件过滤(搜索name中包含字符串"Itcast-1"的记录
// 注意:设置条件过滤时,数据库SQL语句中的%要用*来代替,所以%Itcast-1%应该写成*Itcast-1*)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*Itcast-1*"];
request.predicate = predicate;
// 3. 执行请求
NSError *error = nil;
NSArray *objs = [context executeFetchRequest:request error:&error];
if (error) {
[NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
}
// 4. 遍历数据
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]
}
4)删除数据
// 1. 传入需要删除的实体对象
[context deleteObject:managedObject];
// 2. 将结果同步到数据库
NSError *error = nil;
[context save:&error];
if (error) {
[NSException raise:@"删除错误" format:@"%@", [error localizedDescription]];
}
V. 打开 CoreData 的 SQL 语句输出开关
1 打开Product,点击 EditScheme
2 点击 Arguments,在 ArgumentsPassed On Launch 中添加2项
-com.apple.CoreData.SQLDebug
-
1
VI. 创建 NSManagedObject 的子类
默认的,用 Core Data 取出的实体都是 NSManagedObject 类型,能用 键-值对 来存取数据
但一般情况下,实体在存取数据的基础上,有时还需要添加一些业务方法来完成一些其他任务,那么就必须创建NSManagedObject 的子类
-
选择模型文件
-
选择需要创建子类的实体
-
创建完毕后多了两个子类
文件内容如下
- Person.h
#import
#import
@class Card;
@interface Person : NSManagedObject
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * age;
@property (nonatomic, retain) Card *card;
@end
- Person.m
#import "Person.h"
@implementation Person
@dynamic name;
@dynamic age;
@dynamic card;
@end
- Card.h
#import
#import
@class Person;
@interface Card : NSManagedObject
@property (nonatomic, retain) NSString * no;
@property (nonatomic, retain) Person *person;
@end
- Card.m
#import "Card.h"
#import "Person.h"
@implementation Card
@dynamic no;
@dynamic person;
@end
- 数据库中添加数据 示例
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
person.name = @"SunFarrell";
person.age = [NSNumber numberWithInt:23];
card.no = @"23323323333";
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
person.card = card;
// 最后调用 [context save&error]; 保存数据