阅读视图
iOS应用数据持久化 FMDB
iOS应用数据持久化 SQLite
iOS应用数据持久化 SQLite
一、前言
虽然平时开发不会直接使用
SQLite,但是作为一个轻量级数据库,理解和掌握其基本原理和SQL语句基本使用还是有必要的。FMDB就是基于SQLite的封装,并且微信团队在自研自己的数据库前也是使用SQLite。
点击项目名称 -> TAGETS -> Build Phases -> Link Binary With Libraries 点击添加libsqlite3.tbd:
二、代码准备
@interface ViewController (){
/**数据库*/
sqlite3 *database;
/**准备语句*/
sqlite3_stmt *stmt;
}
@property(nonatomic, strong) NSString *databasePath;
/**id文本框*/
@property (weak, nonatomic) IBOutlet UITextField *idTF;
/**名字文本框*/
@property (weak, nonatomic) IBOutlet UITextField *nameTF;
/**年龄文本框*/
@property (weak, nonatomic) IBOutlet UITextField *ageTF;
模拟器先拉好UI:
1. 生成路径
- (NSString *)databasePath{
if (!_databasePath) {
/**获取Document路径*/
NSArray *Paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *DocumentPath = [Paths firstObject];
/**拼接数据库位置*/
_databasePath = [DocumentPath stringByAppendingPathComponent:@“person_info.sqlite"];
NSLog(@"%@",_databasePath);
}
return _databasePath;
}
2. 打开数据库
/**打开数据库*/
- (void)openDatabase{
/**创建或打开数据库*/
int openFlag = sqlite3_open_v2(self.databasePath.UTF8String, &database, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
/**判断是否成功*/
if (openFlag != SQLITE_OK) {
/**失败则关闭数据库*/
sqlite3_finalize(stmt);
sqlite3_close(self->database);
NSLog(@"创建或打开数据库失败 --- %d",openFlag);
}else{
NSLog(@"创建或打开数据库成功");
[self createForm];
}
}
3. 创建表
/** 创建表*/
- (void)createForm{
char *errMsg = NULL;
/** 创建表
1、建表格式: create table if not exists 表名 (列名 类型,....)
2、如需生成默认增加的id: id integer primary key autoincrement
3、数据库名、表名、字段名使用小写,关键字、函数名称使用大写
4、每个sql语句最后都要加上”;“
*/
NSString *sqlStr = [NSString stringWithFormat:
@"CREATE TABLE IF NOT EXISTS 'person'("
"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"name TEXT NOT NULL,"
"age INTEGER NOT NULL);"];
/**
第1个参数:数据库对象
第2个参数:sql语句
第3个参数:查询时候用到的一个结果集闭包
第4个参数:用不到
第5个参数:错误信息
*/
int execRst = sqlite3_exec(database, sqlStr.UTF8String, NULL, NULL, &errMsg);
if (execRst == SQLITE_OK) {
NSLog(@"创建表成功");
}else{
NSLog(@"创建表失败:%s",errMsg);
}
}
运行打印:
Sqlite3Demo[46764:3296271] /Users/xxxx/Library/Developer/CoreSimulator/Devices/…/data/Containers/Data/Application/…/Documents/Person.sqlite
Sqlite3Demo[46764:3296271] 创建或打开数据库成功
Sqlite3Demo[46764:3296271] 创建表成功
打开生成路径,可以看到一个person_info.sqlite的文件:
用Navicat打开person_info.sqlite,person的表已经创建好了:
4. 插入多条测试数据
/**插入多条数据*/
- (IBAction)insertMultipleData:(UIButton *)sender{
[self openDatabase];
for (int i = 0; i < 10; i++) {
NSString *name = [NSString stringWithFormat:@"张%d",i+1];
int age = arc4random_uniform(20) + 10;
// 拼接 sql 语句
NSString *sql = [NSString stringWithFormat:@"INSERT INTO person (name,age) VALUES ('%@',%d);",name,age];
// 执行 sql 语句
char *errMsg = NULL;
int result = sqlite3_exec(database, sql.UTF8String, NULL, NULL, &errMsg);
if (result == SQLITE_OK) {
NSLog(@"插入数据成功 - %@",name);
} else {
NSLog(@"插入数据失败 - %s",errMsg);
}
}
}
点击模拟器增多条:
Sqlite3Demo[47220:3310420] 插入数据成功 - 张1
Sqlite3Demo[47220:3310420] 插入数据成功 - 张2
Sqlite3Demo[47220:3310420] 插入数据成功 - 张3
Sqlite3Demo[47220:3310420] 插入数据成功 - 张4
Sqlite3Demo[47220:3310420] 插入数据成功 - 张5
Sqlite3Demo[47220:3310420] 插入数据成功 - 张6
Sqlite3Demo[47220:3310420] 插入数据成功 - 张7
Sqlite3Demo[47220:3310420] 插入数据成功 - 张8
Sqlite3Demo[47220:3310420] 插入数据成功 - 张9
Sqlite3Demo[47220:3310420] 插入数据成功 - 张10
5. 添加查询
/**查询所有记录*/
- (IBAction)retrieveAllData:(UIButton *)sender {
[self openDatabase];
NSString *searchSqlStr = @"SELECT * FROM person";
[self operationData:searchSqlStr.UTF8String];
}
/**操作数据*/
- (void)operationData:(const char *)sql{
int result = sqlite3_prepare_v2(database, sql, -1, &stmt, nil);
if (result != SQLITE_OK) {
NSLog(@"操作失败,%d",result);
} else {
/**打印操作后的数据,每调用一次sqlite3_step,stmt就会指向下一条记录*/
printf("------------------------------\n");
/**找到一条记录*/
while (sqlite3_step(stmt) == SQLITE_ROW) {
/**取出第0列字段的值*/
int ID = sqlite3_column_int(stmt, 0);
/**取出第1列字段的值*/
const unsigned char *name = sqlite3_column_text(stmt, 1);
/*取出第2列字段的值*/
int age = sqlite3_column_int(stmt, 2);
printf("查到的数据 id:%d name:%s 年龄:%d\n",ID,name,age);
}
/*关闭连接*/
[self clearTextField];
sqlite3_finalize(stmt);
sqlite3_close(self->database);
}
}
点击查全部:
------------------------------
查到的数据 id:1 name:张1 年龄:29
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:3 name:张3 年龄:26
查到的数据 id:4 name:张4 年龄:29
查到的数据 id:5 name:张5 年龄:18
查到的数据 id:6 name:张6 年龄:26
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:10 name:张10 年龄:26
到Navicat里看一下,已经有10条记录:
还有一个sqlite_sequence,seq是10:
注:如果没有看到数据,点击左边下面的按钮刷新,右键刷新无效。
三、CRUD增删改查操作
1. 增加一条数据
/**插入一条记录*/
- (IBAction)insertData:(UIButton *)sender {
[self openDatabase];
NSString *insertSplStr = [NSString stringWithFormat:@"INSERT INTO person (name,age) VALUES ('%@',%@);",_nameTF.text,_ageTF.text];
[self updateDataWithSql:insertSplStr.UTF8String success:^{
[self clearTextField];
NSLog(@"插入数据成功");
}];
}
name填入夏洛特,age填24,点击增:
Sqlite3Demo[47564:3319611] 插入数据成功
点击查全部,发现已经插入了一条数据:
------------------------------
查到的数据 id:1 name:张1 年龄:29
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:3 name:张3 年龄:26
查到的数据 id:4 name:张4 年龄:29
查到的数据 id:5 name:张5 年龄:18
查到的数据 id:6 name:张6 年龄:26
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:10 name:张10 年龄:26
查到的数据 id:11 name:夏洛特 年龄:24
回到Navicat的person表点击刷新,可以看到新增的一条记录:
2. 删除一条数据
/**删除一条记录*/
- (IBAction)deleteData:(UIButton *)sender {
[self openDatabase];
NSString *deleteSplStr = [NSString stringWithFormat:@"DELETE FROM person where id = %@;",self.idTF.text];
[self updateDataWithSql:deleteSplStr.UTF8String success:^{
[self clearTextField];
NSLog(@"删除数据成功");
}];
}
把id = 5 的数据删除掉:
点击删除:
Sqlite3Demo[47564:3319611] 删除数据成功
查看全部数据,发现id为5的数据已经没有了:
------------------------------
查到的数据 id:1 name:张1 年龄:29
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:3 name:张3 年龄:26
查到的数据 id:4 name:张4 年龄:29
查到的数据 id:6 name:张6 年龄:26
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:10 name:张10 年龄:26
查到的数据 id:11 name:夏洛特 年龄:24
在Navicate里查看:
3. 修改一条数据
/**更新一条记录*/
- (IBAction)updateData:(UIButton *)sender {
[self openDatabase];
NSString *changeSqlStr = [NSString stringWithFormat:@"UPDATE person SET name = '%@' WHERE id = '%@';",self.nameTF.text,self.idTF.text];
[self updateDataWithSql:changeSqlStr.UTF8String success:^{
[self clearTextField];
NSLog(@"修改成功");
}];
}
/**更新记录操作*/
- (void)updateDataWithSql:(const char *)sql success:(void(^)(void))successBlock {
/**
第1个参数:一个已经打开的数据库对象
第2个参数:sql语句
第3个参数:参数2中取出多少字节的长度,-1 自动计算,\0停止取出
第4个参数:准备语句
第5个参数:通过参数3,取出参数2的长度字节之后,剩下的字符串
*/
int result = sqlite3_prepare_v2(database, sql, -1, &stmt, nil);
if (result != SQLITE_OK) {
NSLog(@"操作失败,%d",result);
sqlite3_finalize(stmt);
sqlite3_close(self->database);
} else {
sqlite3_step(stmt);
successBlock();
}
}
把id = 10的名字改为赵云:
查看全部:
------------------------------
查到的数据 id:1 name:张1 年龄:29
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:3 name:张3 年龄:26
查到的数据 id:4 name:张4 年龄:29
查到的数据 id:6 name:张6 年龄:26
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:10 name:赵云 年龄:26
查到的数据 id:11 name:夏洛特 年龄:24
4. 查找检索数据
/**查询记录*/
- (IBAction)retrieveData:(UIButton *)sender {
[self openDatabase];
/**
查询age < 25也可使用sql= "select * from person where age < 25"
*/
NSString *sqlStr;
if(_idTF.text.length > 0){
sqlStr = [NSString stringWithFormat:@"SELECT id, name, age FROM person WHERE id = '%@';",_idTF.text];
}
else if(_nameTF.text.length > 0){
sqlStr = [NSString stringWithFormat:@"SELECT id, name, age FROM person WHERE name = '%@';",_nameTF.text];
}
else if(_ageTF.text.length > 0){
sqlStr = [NSString stringWithFormat:@"SELECT id,name,age FROM person WHERE age %@;",_ageTF.text];
NSLog(@"sqlStr == %@",sqlStr);
}
[self operationData:sqlStr.UTF8String];
}
查id = 4的记录:
------------------------------
查到的数据 id:4 name:张4 年龄:29
查年龄小于25的记录:
点击查按钮:
------------------------------
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:11 name:夏洛特 年龄:24
查找名字赵云:
------------------------------
查到的数据 id:10 name:赵云 年龄:26
点击增加多条数据测试,再查看全部数据:
------------------------------
查到的数据 id:1 name:张1 年龄:29
查到的数据 id:2 name:张2 年龄:16
查到的数据 id:3 name:张3 年龄:26
查到的数据 id:4 name:张4 年龄:29
查到的数据 id:6 name:张6 年龄:26
查到的数据 id:7 name:张7 年龄:20
查到的数据 id:8 name:张8 年龄:17
查到的数据 id:9 name:张9 年龄:14
查到的数据 id:10 name:赵云 年龄:26
查到的数据 id:11 name:夏洛特 年龄:24
查到的数据 id:12 name:张1 年龄:21
查到的数据 id:13 name:张2 年龄:11
查到的数据 id:14 name:张3 年龄:20
查到的数据 id:15 name:张4 年龄:28
查到的数据 id:16 name:张5 年龄:25
查到的数据 id:17 name:张6 年龄:24
查到的数据 id:18 name:张7 年龄:22
查到的数据 id:19 name:张8 年龄:29
查到的数据 id:20 name:张9 年龄:14
查到的数据 id:21 name:张10 年龄:26
查看Navicat的person:
查看squence,点击刷新,id为21完全一致:
点击清空:
/**清空表中数据和自增排序*/
- (IBAction)removeAllData:(UIButton *)sender {
[self openDatabase];
const char *deleteSpl = "DELETE FROM person";
[self updateDataWithSql:deleteSpl success:^{
NSLog(@"清空表数据成功");
}];
}
表数据已经被清空:
这个时候sqlite_sequence还是21,没有归0:
此时点击插入10条数据,发现id从21开始:
并没有真正的清空,需要给数据添加seq归0操作:
sqlite3 没有truncate关键字:
/**清空表中数据和自增排序*/
- (IBAction)removeAllData:(UIButton *)sender {
[self openDatabase];
const char *deleteSpl = "DELETE FROM person";
[self updateDataWithSql:deleteSpl success:^{
NSLog(@"清空表数据成功");
const char *setSeqSpl = "UPDATE sqlite_sequence SET seq = 0 WHERE name = 'person'";
[self updateDataWithSql:setSeqSpl success:^{
NSLog(@"自增主键归零");
}];
}];
}
再点击清空:
点击插入多条,id又从1开始:
四、总结
SQLite接口都是C语言,并不面向对象,所有操作需要自己手写SQL语句。
参考
对象序列化
YYCache(二)
YYCache(一)
Fastlane自动化打包到蒲公英
离屏渲染(二)
离屏渲染(一)
启动优化clang插桩(三)
启动优化clang插桩(二)
启动优化clang插桩(一)
启动优化clang插桩(一)
一、了解Clang
首先到Clang地址:Clang Documentation
PCs指的是CPU的寄存器,用来存储将要执行的下一条指令的地址,Tracing PCs就是跟踪CPU将要执行的代码。
二、如何使用
网页下拉有个Example
使用之前要在工程添加标记:
编译器就会在每一行代码的边缘插入这一段函数:
__sanitizer_cov_trace_pc_guard(&guard_variable)
打开实例demo,在Build Settings 搜索 Other c Flag 填入 -fsanitize-coverage=trace-pc-guard
项目会报未定义符号的错:
这就需要去定义这两个符号,先把这两个函数复制过来:
先把代码复制进
ViewController
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
// if(*guard)
// __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
// __sanitizer_cov_trace_pc_guard(guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
// If you set *guard to 0 this code will not be called again for this edge.
// Now you can get the PC and do whatever you want:
// store it somewhere or symbolize it and print right away.
// The values of `*guard` are as you set them in
// __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
// and use them to dereference an array or a bit vector.
void *PC = __builtin_return_address(0);
char PcDescr[1024];
// This function is a part of the sanitizer run-time.
// To use it, link with AddressSanitizer or other sanitizer.
__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
把头文件也粘贴进来:
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
两个方法里面都有
extern “C”,extern “C”的主要作用是为了能够正确实现C++去调用其他C语言的代码,加上extern “C”就会指示作用域内的代码按照C语言区编译,而不是C++,这个extern “C”在OC项目里没什么用,直接删除
此时还会包一个错误:
这个__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));函数没有什么作用,直接删除即可。
三、代码调试
cmd + r运行,此时终端会打印一些信息:
删除两个函数里面的注释,先注释第二个的内容,然后运行
INIT: 0x1025c5478 0x1025c54f0
这是运行打印得到的地址,就是函数(uint32_t *start, uint32_t *stop)的start和stop两个指针的地址
stop存储的就是我们工程里面符号的个数
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
看一下这个
for循环,start会先复制给*x,x++就是内存平移,按照uint32_t的大小去平移,而uint32_t的定义是typedef unsigned int uint32_t;是无符号整型,占4个字节,所以每次按4个字节平移。
start和stop里面存的是什么,打断点调试:
先看start:
INIT: 0x1042a5278 0x1042a52e0
(lldb) x 0x1042a5278
0x1042a5278: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ................
0x1042a5288: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 ................
(lldb)
由于uint32_t按4个字节来存储发现start就是 0 1 2 3 4…,再看stop,由于stop的已经是结束位置,读取的数据是在start和stop之间的数据,所以需要向前平移4个字节得到其真实数据。
(lldb) x (0x1042a52e0-4)
0x1042a52dc: 1a 00 00 00 00 00 00 00 00 00 00 00 fe f1 29 04 ..............).
0x1042a52ec: 01 00 00 00 00 00 00 00 00 00 00 00 90 40 2a 04 .............@*.
(lldb)
可以得到1a 就是26,也可以循环外面打印结果:
可以得到:
TraceDemo[16814:301325] 26
也是26个符号。
四、测试验证方法
可以验证一下,添加一个函数:
void test(void) {
NSLog(@"%s",__func__);
}
符号变成27:
TraceDemo[16911:304537] 27
再添加一个block:
void (^block) (void) = ^{
NSLog(@"%s",__func__);
};
符号变成28:
TraceDemo[16933:305465] 28
添加一个数据类型属性:
@property (nonatomic ,assign) int age;
由于系统自动生成getter、setter方法,符号变成30
TraceDemo[16975:306816] 30
添加一个对象属性:
@property (nonatomic ,copy) NSString *str;
符号变成33:
TraceDemo[17041:308780] 33
对象属性由于ARC,系统自动除了生成getter、setter方法外还生成了cxx_destruct()析构函数
添加一个方法:
- (void)test{
}
符号变成34:
TraceDemo[17114:311256] 34
在其他类AppDelegate类中添加一个属性:
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) NSString *name;
@end
符号变成37:
TraceDemo[17266:316294] 37
符号变成37,
结论
这就说明了通过这个方法整个项目里的符号,它都能捕获到。