iOS 数据存储

[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 条件;

通配符

  1. %:代替 任意0个或多个 字符
  • 可匹配任意类型和长度的字符
  • 有些情况下若是中文,使用两个百分号%%表示。
  • %三%:可以搜出所有含有 三 的记录
  • %三%猫:能够搜出「三猫」,却搜不出「猫三」
  • 要搜出即含有「猫」,又含有「三」的所遇记录使用
    SELECT 字段1 FROM 表名 WHERE 字段2 LIKE '%三%' AND 字段2 LIKE '%猫%';
  1. _:代替 任意单个 字符
  • 常用来限制表达式的字符长度语句
  • _三_:只能找出是 三个字 且 中间是「三」的记录
  • 三__:只能找出是 三个字 且 首字是「三」的记录
  1. []:代替括号内所列字符中的一个「类似正则表达式」
  • 指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个
  • [张李王]三:将找出「张三」、「李三」、「王三」而不是「张李王三」
  1. [^]:代替不在括号所列之内的单个字符
  • 要求所匹配对象为指定字符以外的 任一一个 字符。
  • [^张李王]三:找出不姓「张」、「李」、「王」的「赵三」、「孙三」等
  1. 查询内容包含通配符时
  • 查询特殊字符%_[
  • 把特殊字符用[]括起便可正常查询
  • 例:like '5[%]' 表示查询含有 5% 的字段
  1. 通配符的综合使用
  • 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 <sqlite3.h>

  • 模型对象数据要存储到 数据库中 先用 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 和 导入主头文件 <CoreData/CoreData.h>

特点

  • 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)创建模型文件

  1. 例子 模型文件


  2. 选择模板




  3. 添加实体


  4. 添加 Person 属性
  1. 添加 Card 属性


  2. 建立 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 的子类

  1. 选择模型文件


  2. 选择需要创建子类的实体


  3. 创建完毕后多了两个子类


文件内容如下

  • Person.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@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 <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@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]; 保存数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容