阅读视图

发现新文章,点击刷新页面。

iOS应用数据持久化 FMDB

iOS应用数据持久化 FMDB

FMDB是对SQLite的面向对象封装,主要包含 3 个类:

  1. FMDatabase 表示单个SQLite数据库,用于执行SQL语句;
  2. FMResultSet 表示对FMDatabase执行查询的结果;
  3. FMDatabaseQueue 如果你想在多个线程上执行查询和更新,需要使用这个类。

一、数据库测试准备

1. 创建或打开数据库

/**创建或打开数据库
 */
- (void)openDB{
    if(self.db.isOpen){
        NSLog(@"数据库已经打开");
    }else{
        NSString *docsPath = NSSearchPathForDirectoriesInDomains
        (NSDocumentDirectory, NSUserDomainMask, YES)[0];
        NSString *dbPath   = [docsPath stringByAppendingPathComponent:
        @"person_info.db"];
        NSLog(@"path == %@",dbPath);
        FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
        _db = db;
        if (![self.db open]) {
            self.db = nil;
            NSLog(@"数据库打开失败");
            return;
        }else{
            if(self.db.goodConnection){
                NSLog(@"数据库连接成功");
                [self.db open];
                if(self.db.isOpen){
                    NSLog(@"数据库已经打开");
                    [self createTable];
                }
            }
            else{
                NSLog(@"数据库连接失败");
            }
        }
    }
}

2. 创建表

/**创建表
 */
- (void)createTable{
    NSString *sql = [NSString stringWithFormat:
                     @"CREATE TABLE IF NOT EXISTS 'person'("
                     "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
                     "name TEXT NOT NULL,"
                     "age INTEGER NOT NULL);"];
    BOOL success = [_db executeStatements:sql];
    if (success) {
        NSLog(@"创建或打开表成功");
    } else {
        NSLog(@"创建表或打开表失败 - %@",[_db lastErrorMessage]);
    }
}

3. 准备好UI界面

Pasted Graphic.png

点击模拟器查全部按钮,打开数据库和创建表:

FMDBDemo[95834:1335897] path == /Users/xxxxx/Library/Developer
/CoreSimulator/Devices/.../data/Containers/Data/Application/
…/Documents/person_info.db
FMDBDemo[95834:1335897] 数据库连接成功
FMDBDemo[95834:1335897] 数据库已经打开
FMDBDemo[95834:1335897] 创建或打开表成功
FMDBDemo[95834:1335897] 检索数据成功
----------------------------------
FMDBDemo[95834:1335897] person表没有数据

4. 添加多条数据

FMDB,任何类型的不是SELECT查询语句的 SQL 语句都可以使用更新executeUpdate:语句。这包括CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN,VACUUMREPLACE语句(以及更多)。基本上,如果你的SQL语句不以开头SELECT,它就是一个更新语句。

执行更新返回 BOOL值。返回值为YES表示更新已成功执行,返回值为NO表示遇到错误。可以调用-lastErrorMessage-lastErrorCode方法来检索更多信息。

- (IBAction)insertMultipleData:(UIButton *)sender {
    [self openDB];
    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 语句
        BOOL success = [_db executeUpdate:sql];
        if (success) {
            NSLog(@"插入数据成功 - %@",name);
        } else {
            NSLog(@"插入数据失败 - %@",[_db lastErrorMessage]);
        }
    }
    [_db close];
}

点击模拟器新增多条按钮:

FMDBDemo[95834:1335897] 数据库连接成功
FMDBDemo[95834:1335897] 数据库已经打开
FMDBDemo[95834:1335897] 创建或打开表成功
FMDBDemo[95834:1335897] 插入数据成功 - 张1
FMDBDemo[95834:1335897] 插入数据成功 - 张2
FMDBDemo[95834:1335897] 插入数据成功 - 张3
FMDBDemo[95834:1335897] 插入数据成功 - 张4
FMDBDemo[95834:1335897] 插入数据成功 - 张5
FMDBDemo[95834:1335897] 插入数据成功 - 张6
FMDBDemo[95834:1335897] 插入数据成功 - 张7
FMDBDemo[95834:1335897] 插入数据成功 - 张8
FMDBDemo[95834:1335897] 插入数据成功 - 张9
FMDBDemo[95834:1335897] 插入数据成功 - 张10

5. 查询全部数据

语句SELECT是一个查询,通过其中一种方法执行-executeQuery,执行查询FMResultSet成功则返回一个对象,nil失败则返回一个对象。您应该使用-lastErrorMessage-lastErrorCode方法来确定查询失败的原因。为了遍历查询结果,您使用了一个while()循环。您还需要从一条记录“步进”到另一条记录。

/**检索全部数据
 */
- (IBAction)retrieveAllData:(UIButton *)sender {
    [self openDB];
    NSString *searchSqlStr = @"SELECT * FROM person";
    BOOL success = [_db executeStatements:searchSqlStr];
    if (success) {
        NSLog(@"检索数据成功");
        FMResultSet *s = [_db executeQuery:searchSqlStr];
        int count = 0;
        printf("----------------------------------\n");
        while ([s next]) {
            printf("查到的数据 id:%s\t名字:%s\t\t年龄:%s\n",
            [s stringForColumn:[s columnNameForIndex:0]].UTF8String,
            [s stringForColumn:[s columnNameForIndex:1]].UTF8String,
            [s stringForColumn:[s columnNameForIndex:2]].UTF8String);
            count ++;
        }
        if(count == 0){
            NSLog(@"person表没有数据");
        }
       
    } else {
        NSLog(@"检索数据失败 - %@",[_db lastErrorMessage]);
    }
    [_db close];
}

点击模拟器查全部按钮:

----------------------------------
查到的数据 id:1名字:张1年龄:22
查到的数据 id:2名字:张2年龄:10
查到的数据 id:3名字:张3年龄:28
查到的数据 id:4名字:张4年龄:25
查到的数据 id:5名字:张5年龄:23
查到的数据 id:6名字:张6年龄:25
查到的数据 id:7名字:张7年龄:19
查到的数据 id:8名字:张8年龄:16
查到的数据 id:9名字:张9年龄:14
查到的数据 id:10名字:张10年龄:10

Navicat里查看:

Pasted Graphic 2.png

6. 清空表数据

/**清空表数据
 */
- (IBAction)removeAllData:(UIButton *)sender {
    [self openDB];
    NSString *sql = @"DELETE FROM person";
    // 执行 sql 语句
    BOOL success = [_db executeUpdate:sql];
    if (success) {
        NSLog(@"清空表数据成功");
        NSString *setSeqSpl = @"UPDATE sqlite_sequence SET seq = 0
        WHERE name = 'person'";
        //        BOOL success = [_db executeStatements:setSeqSpl];
        BOOL success = [_db executeUpdate:setSeqSpl];
        if (success) {
            NSLog(@"自增主键归零");
        }else{
            NSLog(@"自增主键归零失败 - %@",[_db lastErrorMessage]);
        }
    } else {
        NSLog(@"清空表数据失败 - %@",[_db lastErrorMessage]);
    }
    [_db close];
}

点击模拟器清空全部按钮:

FMDBDemo[95834:1335897] 数据库连接成功
FMDBDemo[95834:1335897] 数据库已经打开
FMDBDemo[95834:1335897] 创建或打开表成功
FMDBDemo[95834:1335897] 清空表数据成功
FMDBDemo[95834:1335897] 自增主键归零

Navicat里查看表,数据确实被清空:

Pasted Graphic 3.pngsqlite_sequenece也变为 0

Pasted Graphic 4.png

二、增删改查 操作

1. 新增操作

/**增
 */
- (IBAction)insertData:(UIButton *)sender {
    [self openDB];
    NSString *insertSplStr = [NSString stringWithFormat:@"INSERT INTO
    person (name,age) 
    VALUES ('%@',%@);",_nameTF.text,_ageTF.text];
    BOOL success = [_db executeUpdate:insertSplStr];
    if (success) {
        NSLog(@"插入数据成功");
    } else {
        NSLog(@"插入数据失败 - %@",[_db lastErrorMessage]);
    }
    [self clearTextField];
}

模拟器添加数据,点击按钮:

Pasted Graphic 5.png

运行:

FMDBDemo[95834:1335897] 数据库连接成功
FMDBDemo[95834:1335897] 数据库已经打开
FMDBDemo[95834:1335897] 创建或打开表成功
FMDBDemo[95834:1335897] 插入数据成功

查看全部,就有一条数据:

----------------------------------
查到的数据 id:1名字:鲁班年龄:26

点击新增多条数据:

----------------------------------
查到的数据 id:1名字:鲁班年龄:26
查到的数据 id:2名字:张1年龄:21
查到的数据 id:3名字:张2年龄:16
查到的数据 id:4名字:张3年龄:21
查到的数据 id:5名字:张4年龄:21
查到的数据 id:6名字:张5年龄:13
查到的数据 id:7名字:张6年龄:17
查到的数据 id:8名字:张7年龄:24
查到的数据 id:9名字:张8年龄:24
查到的数据 id:10名字:张9年龄:13
查到的数据 id:11名字:张10年龄:28

2. 删除操作

/**删
 */
- (IBAction)deleteData:(UIButton *)sender {
    [self openDB];
    NSString *deleteSplStr = [NSString stringWithFormat:@"DELETE FROM
    person where id = %@;",
    self.idTF.text];
    BOOL success = [_db executeUpdate:deleteSplStr];
    if (success) {
        NSLog(@"删除数据成功");
    } else {
        NSLog(@"删除数据失败 - %@",[_db lastErrorMessage]);
    }
    [self clearTextField];
}

删除id5 的数据:

Pasted Graphic 6.png

点击按钮,查看打印数据,id5 的数据已被删除:

----------------------------------
查到的数据 id:1名字:鲁班年龄:26
查到的数据 id:2名字:张1年龄:21
查到的数据 id:3名字:张2年龄:16
查到的数据 id:4名字:张3年龄:21
查到的数据 id:6名字:张5年龄:13
查到的数据 id:7名字:张6年龄:17
查到的数据 id:8名字:张7年龄:24
查到的数据 id:9名字:张8年龄:24
查到的数据 id:10名字:张9年龄:13
查到的数据 id:11名字:张10年龄:28

3. 更新操作

/**改
 */
- (IBAction)updateData:(UIButton *)sender {
    [self openDB];
    
    NSString *changeSqlStr = [NSString stringWithFormat:@"UPDATE
    person SET name = '%@' 
    WHERE id = '%@';",self.nameTF.text,self.idTF.text];
    BOOL success = [_db executeUpdate:changeSqlStr];
    if (success) {
        NSLog(@"修改数据成功");
    } else {
        NSLog(@"修改数据失败 - %@",[_db lastErrorMessage]);
    }
    [self clearTextField];
}

id10 的名字改为妲己:

Pasted Graphic 7.png 点击按钮,查看打印数据:

----------------------------------
查到的数据 id:1名字:鲁班年龄:26
查到的数据 id:2名字:张1年龄:21
查到的数据 id:3名字:张2年龄:16
查到的数据 id:4名字:张3年龄:21
查到的数据 id:6名字:张5年龄:13
查到的数据 id:7名字:张6年龄:17
查到的数据 id:8名字:张7年龄:24
查到的数据 id:9名字:张8年龄:24
查到的数据 id:10名字:妲己年龄:13
查到的数据 id:11名字:张10年龄:28

4. 查找数据

- (IBAction)retrieveData:(UIButton *)sender {
    [self openDB];
    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);
    }
    BOOL success = [_db executeStatements:sqlStr];
    if (success) {
        FMResultSet *s = [_db executeQuery:sqlStr];
        while ([s next]) {
            printf("查到的数据 id:%s\t名字:%s\t\t年龄:%s\n",
            [s stringForColumn:[s columnNameForIndex:0]].UTF8String,
            [s stringForColumn:[s columnNameForIndex:1]].UTF8String,
            [s stringForColumn:[s columnNameForIndex:2]].UTF8String);
        }
    } else {
        NSLog(@"检索数据失败 - %@",[_db lastErrorMessage]);
    }
    [self clearTextField];
}

/**清空输入查询
 */
- (void)clearTextField{
    self.idTF.text = @"";
    self.nameTF.text = @"";
    self.ageTF.text = @"";
    [_db close];
}

查询年龄大于 25 的数据:

Pasted Graphic 8.png

点击按钮,发现 2 条数据符合:

----------------------------------
查到的数据 id:1名字:鲁班年龄:26
查到的数据 id:11名字:张10年龄:28

注意,当您完成对数据库的查询和更新后,您应该-close建立FMDatabase连接,以便SQLite放弃它在操作过程中获得的任何资源。

三、总结

FMDB是对SQLite的一层包装,相对于SQLite会更加面向对象,但仍然需要自己手写SQL语句。FMDB支持多条语句和批处理,可以使用FMDatabaseexecuteStatements:withResultBlock: 在字符串中执行多个语句。 如果需要线程安全,可以使用FMDatabaseQueue,支持跨多个线程访问。并且,支持事务处理,如果事务,如果事务失败会回滚。

参考

离屏渲染(二)

离屏渲染是在当前屏幕帧缓冲区外增加了一个临时新的缓冲区对将要进行显示的图片进行渲染操作再写回帧缓冲区,过个过程造成性能损耗。

离屏渲染(一)

离屏渲染是在当前屏幕帧缓冲区外增加了一个临时新的缓冲区对将要进行显示的图片进行渲染操作再写回帧缓冲区,过个过程造成性能损耗。

启动优化clang插桩(一)

启动优化clang插桩(一)

一、了解Clang

首先到Clang地址:Clang Documentation Pasted Graphic.pngPCs指的是CPU的寄存器,用来存储将要执行的下一条指令的地址,Tracing PCs就是跟踪CPU将要执行的代码。

二、如何使用

网页下拉有个Example Pasted Graphic 1.png 使用之前要在工程添加标记: Pasted Graphic 2.png

编译器就会在每一行代码的边缘插入这一段函数:__sanitizer_cov_trace_pc_guard(&guard_variable)

打开实例demo,在Build Settings 搜索 Other c Flag 填入 -fsanitize-coverage=trace-pc-guard

1__#$!@%!#__Pasted Graphic 1.png

项目会报未定义符号的错:

Pasted Graphic 7.png

这就需要去定义这两个符号,先把这两个函数复制过来:

Pasted Graphic 5.png 先把代码复制进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项目里没什么用,直接删除

此时还会包一个错误:

Pasted Graphic 8.png

这个__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));函数没有什么作用,直接删除即可。

三、代码调试

cmd + r运行,此时终端会打印一些信息:

Pasted Graphic 9.png

删除两个函数里面的注释,先注释第二个的内容,然后运行

INIT: 0x1025c5478 0x1025c54f0

这是运行打印得到的地址,就是函数(uint32_t *start, uint32_t *stop)startstop两个指针的地址

stop存储的就是我们工程里面符号的个数

for (uint32_t *x = start; x < stop; x++)
        *x = ++N;

看一下这个for循环,start会先复制给*xx++就是内存平移,按照uint32_t的大小去平移,而uint32_t的定义是typedef unsigned int uint32_t; 是无符号整型,占4个字节,所以每次按4个字节平移。

startstop里面存的是什么,打断点调试:

Pasted Graphic 10.png

先看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_t4个字节来存储发现start就是 0 1 2 3 4…,再看stop,由于stop的已经是结束位置,读取的数据是在startstop之间的数据,所以需要向前平移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,也可以循环外面打印结果: Pasted Graphic 11.png 可以得到:

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

结论

这就说明了通过这个方法整个项目里的符号,它都能捕获到。

❌