(原理篇)基于SQLite3轻量级封装,一行代码实现增删改查

最近写的项目中有用到数据库,写了很多蛋疼的sql语句,每次都是好几行代码,并且每次都是重复的没有一点技术含量的代码,虽然也有很多基于sqlite的封装,不过用起来仍是感受不够面向对象!
为了避免再写重复的代码,花了几天时间,基于SQLite3简单封装了下,实现了一行代码解决增删改查等经常使用的功能!并无太太高深的知识,主要用了runtime和KVC:git

首先咱们建立个你们都熟悉的Person类,并声明两个属性,下面将以类此展开分析github

 

1sql

2数据库

3json

4数组

@interface Person : NSObjectapp

@property(nonatomiccopyNSString *name;atom

@property(nonatomicassignNSInteger age;spa

@end.net

 

建立表格

相信下面这句创表语句你们都熟悉吧,就不作介绍了

create table if not exists Person (id integer primary key autoincrement,name text,age integer)

然而开发中咱们都是基于模型开发的,基本上都是一个模型对应数据库的一张表,那么每一个模型的属性都不同,那么咱们又该如何生成相似上面的语句呢? 我想到了runtime,经过runtime获取一个类的属性列表,因此有了下面这个方法:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

/// 获取当前类的全部属性

+ (NSArray *)getAttributeListWithClass:(id)className {

    // 记录属性个数

    unsigned int count;

    objc_property_t *properties = class_copyPropertyList([className class], &count);

     

    NSMutableArray *tempArrayM = [NSMutableArray array];

     

    for (int i = 0; i < count; i++) {

         

        // objc_property_t 属性类型

        objc_property_t property = properties[i];

         

        // 转换为Objective C 字符串

        NSString *name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];

         

        NSAssert(![name isEqualToString:@"index"], @"禁止在model中使用index做为属性,不然会引发语法错误");

         

        if ([name isEqualToString:@"hash"]) {

            break;

        }

         

        [tempArrayM addObject:name];

    }

    free(properties);

    return [tempArrayM copy];

}

 

经过这个方法咱们能够获取一个类的全部属性列表并将其保存到数组中(index是数据库中保留的关键字,因此在这里用了个断言),然而仅仅是拿到属性列表仍是不够的,咱们还须要将对应的OC类型转换为SQL对应的数据类型,相信经过上面获取属性名的方法,你们也知道经过runtime能拿到属性对应的数据类型了,那么咱们能够经过下面方法将其转换为SQLite须要的类型

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/// OC类型转SQL类型

+ (NSString *)OCConversionTyleToSQLWithString:(NSString *)String {

    if ([String isEqualToString:@"long"] || [String isEqualToString:@"int"] || [String isEqualToString:@"BOOL"]) {

        return @"integer";

    }

    if ([String isEqualToString:@"NSData"]) {

        return @"blob";

    }

    if ([String isEqualToString:@"double"] || [String isEqualToString:@"float"]) {

        return @"real";

    }

    // 自定义数组标记

    if ([String isEqualToString:@"NSArray"] || [String isEqualToString:@"NSMutableArray"]) {

        return @"customArr";

    }

    // 自定义字典标记

    if ([String isEqualToString:@"NSDictionary"] || [String isEqualToString:@"NSMutableDictionary"]) {

        return @"customDict";

    }

    return @"text";

}

经过上面方法咱们将OC的数据类型转换为了SQL的数据类型并保存到了数组中(上面有两个自定义的类型,后面使用到的时候再作介绍),经过上面的方法咱们成功的拿到了一个模型类的属性名和对应的SQL数据类型,而后使用键值对的形式将其保存到了一个字典中,好比:

1

@{@"name" @"text",@"age":"integer"};

获取到这些以后那么创表语句就不难了吧,

// 该方法接收一个类型,内部经过遍历类的属性,字符串拼接获取完整的创表语句,并在内部执行sql语句,并返回结果
- (BOOL)creatTableWithClassName:(id)className;

介绍完了怎么创表,那么咱们再来讲说怎么将数据插入到数据库中:
咱们先看一看插入数据的sql语句:insert into Person (name,age) values ('花菜ChrisCai98',89);
前面都是固定格式的,一样咱们能够经过字符串的拼接获取完整的创表语句;
在上面咱们已经能够拿到Person类的全部属性列表,那么咱们如何拼接sql语句呢? 在这里我定义了这么一个方法

/// 该方法接收一个对象做为参数(模型对象),并返回是否插入成功
- (BOOL)insertDataFromObject:(id)object;
/// 咱们能够这样
Person * p = [[Person alloc]init];
p.name = @"花菜ChrisCai";
p.age = 18;
[[GKDatabaseManager sharedManager] insertDataFromObject:p];

插入数据

经过上面这么简单的一句代码实现将数据插入到数据库中,在该方法内部咱们经过上面所述的方法获取Person类的全部属性列表,那么咱们能够就能够拼接插入语句的前半句了,而后经过KVC的形式完成后半部分赋值的操做;

/// 插入数据
- (BOOL)insertDataFromObject:(id)object {
    // 建立可变字符串用于拼接sql语句
    NSMutableString * sqlString = [NSMutableString stringWithFormat:@"insert into %@ (",NSStringFromClass([object class])];
    [[GKObjcProperty getUserNeedAttributeListWithClass:[object class]] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 拼接字段名
        [sqlString appendFormat:@"%@,",obj];
    }];
    // 去掉后面的逗号
    [sqlString deleteCharactersInRange:NSMakeRange(sqlString.length-1, 1)];
    // 拼接values
    [sqlString appendString:@") values ("];
    
    // 拼接字段值
    [[GKObjcProperty getSQLProperties:[object class]] enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        // 拼接属性
        if ([object valueForKey:key]){
            if ([obj isEqualToString:@"text"]) {
                [sqlString appendFormat:@"'%@',",[object valueForKey:key]];
            } else if ([obj isEqualToString:@"customArr"] || [obj isEqualToString:@"customDict"]) { // 数组字典转处理
                NSData * data = [NSJSONSerialization dataWithJSONObject:[object valueForKey:key] options:0 error:nil];
                NSString * jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)];
                [sqlString appendFormat:@"'%@',",jsonString];
            }else if ([obj isEqualToString:@"blob"]){ // NSData处理
                NSString * jsonString = [[NSString alloc] initWithData:[object valueForKey:key] encoding:(NSUTF8StringEncoding)];
                [sqlString appendFormat:@"'%@',",jsonString];
            }else {
                [sqlString appendFormat:@"%@,",[object valueForKey:key]];
            }
        }else {// 没有值就存NULL
            [sqlString appendFormat:@"'%@',",[object valueForKey:key]];
        }
    }];
    // 去掉后面的逗号
    [sqlString deleteCharactersInRange:NSMakeRange(sqlString.length-1, 1)];
    // 添加后面的括号
    [sqlString appendFormat:@");"];
    // 执行语句
    return [self executeSqlString:sqlString];
}

在上面方法中,咱们用到了以前提到的自定义的类型,经过该自定的类型咱们知道须要存储的是字典或者数组,在这里,咱们将数组和字典转换为JSON字符串的形式存入数据库中;

到此咱们完成了创表和插入向表格中插入数据的操做,下面咱们再看看如何从实现一行代码从数据库中将值取出来,在这里咱们提供了6中查询的接口,

  • 提供的接口以下:

- (NSArray *)selecteDataWithClass:(id)className;// 根据类名查询对应表格内全部数据
- (NSInteger)getTotalRowsFormClass:(id)className; // 获取表的总行数
- (id)selecteFormClass:(id)className index:(NSInteger)index;// 获取指定行数据
- (NSArray *)selectObject:(Class)className key:(id)key operate:(NSString *)operate value:(id)value;// 指定条件查询
- (NSArray *)selecteDataWithSqlString:(NSString *)sqlString class:(id)className;// 自定义语句查询
- (NSArray *)selectObject:(Class)className propertyName:(NSString *)propertyName type:(GKDatabaseSelectLocation)type content:(NSString *)content;// 模糊查询

经过第一个方法(该方法接收一个类名做为参数)就能简单的实现一行代码查询表格中的数据了

 NSArray * persons = [[GKDatabaseManager sharedManager] selecteDataWithClass:[Person class]];

下面咱们着重介绍下核心方法,其余全部方法都是基于该方法实现的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

/// 自定义语句查询

- (NSArray *)selecteDataWithSqlString:(NSString *)sqlString class:(id)className  {

     

    // 建立模型数组

    NSMutableArray *models = nil;

    // 1.准备查询

    sqlite3_stmt *stmt; // 用于提取数据的变量

    int result = sqlite3_prepare_v2(database, sqlString.UTF8String, -1, &stmt, NULL);

    // 2.判断是否准备好

    if (SQLITE_OK == result) {

        models = [NSMutableArray array];

        // 获取属性列表名数组 好比name

        NSArray * arr = [GKObjcProperty getUserNeedAttributeListWithClass:[className class]];

        // 获取属性列表名和sql数据类型 好比  name : text

        NSDictionary * dict = [GKObjcProperty getSQLProperties:[className class]];

        // 准备好了

        while (SQLITE_ROW == sqlite3_step(stmt)) { // 提取到一条数据

            __block id objc = [[[className class] alloc]init];

            for int i = 0; i < arr.count; i++) {

                // 默认第0个元素为表格主键 因此元素从第一个开始

                // 使用KVC完成赋值

                if ([dict[arr[i]] isEqualToString:@"text"]) {

                    [objc setValue:[NSString stringWithFormat:@"%@",[self textForColumn:i + 1  stmt:stmt]] forKey:arr[i]];

                     

                else if ([dict[arr[i]] isEqualToString:@"real"]) {

                    [objc setValue:[NSString stringWithFormat:@"%f",[self doubleForColumn:i + 1  stmt:stmt]] forKey:arr[i]];

                     

                else if ([dict[arr[i]] isEqualToString:@"integer"]) {

                     

                    [objc setValue:[NSString stringWithFormat:@"%i",[self intForColumn:i + 1  stmt:stmt]] forKey:arr[i]];

                     

                else if ([dict[arr[i]] isEqualToString:@"customArr"]) { // 数组处理

                     

                    NSString * str = [self textForColumn:i + 1 stmt:stmt];

                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];

                    NSArray * resultArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

                    [objc setValue:resultArray forKey:arr[i]];

                }  else if ([dict[arr[i]] isEqualToString:@"customDict"]) { // 字典处理

                     

                    NSString * str = [self textForColumn:i + 1 stmt:stmt];

                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];

                    NSDictionary * resultDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

                    [objc setValue:resultDict forKey:arr[i]];

                else if ([dict[arr[i]] isEqualToString:@"blob"]) { // 二进制处理

                     

                    NSString * str = [self textForColumn:i + 1 stmt:stmt];

                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];

                    [objc setValue:data forKey:arr[i]];

                }

            }

            [models addObject:objc];

        }

    }

    return [models copy];

}

在该方法内部,咱们根据传递进来的类建立了一个对象(使用__block是由于在block内部须要修改对象的属性),经过以前的方法咱们拿到了对应的sql类型,和属性名,这里就不重复介绍了,经过对应的sql类型执行对应的方法从数据中将数据取出来,并经过KVC的形式给对象赋值,值得一提的是这里咱们经过自定义的字段(customArr,customDict)能够知道咱们取的是数组或者字典,而后数据库中的JSON字符串转换为数组或者字典,而后再利用KVC赋值给对象!

到此基本上全部的功能就都实现了,其余的诸如更新数据,删除数据,删除表格等有提供具体的接口,这里就不一一介绍了,源码中有详细的注释,同时也有DEMO,有须要的能够自行下载,
源码地址:https://github.com/ChrisCaixx/GKDatabase

相关文章
相关标签/搜索