基于SQLite3轻量级封装,一行代码实现增删改查

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

首先我们创建个大家都熟悉的Person类,并声明两个属性,下面将以类此展开分析


@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end

创建表格
相信下面这句创表语句大家都熟悉吧,就不做介绍了

create table if not exists Person (id integer primary key autoincrement,name text,age integer)
然而开发中我们都是基于模型开发的,基本上都是一个模型对应数据库的一张表,那么每个模型的属性都不一样,那么我们又该如何生成类似上面的语句呢? 我想到了runtime,通过runtime获取一个类的属性列表,所以有了下面这个方法:

/// 获取当前类的所有属性
+ (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需要的类型

/// 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数据类型,然后使用键值对的形式将其保存到了一个字典中,比如:

@{@”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]];

下面我们着重介绍下核心方法,其他所有方法都是基于该方法实现的

/// 自定义语句查询
- (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赋值给对象!

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

以太网帧格式 - 2016-07-24 18:07:25

浅谈以太网帧格式                                       一、Ethernet帧格式的发展 1980 DEC,Intel,Xerox制订了Ethernet I的标准 1982 DEC,Intel,Xerox又制订了Ehternet II的标准 1982 IEEE开始研究Ethernet的国际标准802.3  1983 迫不及待的Novell基于IEEE的802.3的原始版开发了专用的Ethernet帧格式 1985 IEEE推出IEEE 802.3规范,后来为解决Eth
  谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK。而在NDKr7开始,Google的Windows版的NDK提供了一个ndk-build.cmd的脚本,这样,就可以直接利用这个脚本编译,而不需要使用Cygwin了。只需要为Eclipse Android工程添加一个Builders,而为Eclipse配置的builder,其实就是在执行Cygwin,然后传递ndk-build作为参数,这样就能让Eclipse自动编译
文章系列 视频驱动V4L2子系统驱动架构 - 驱动框架 视频驱动V4L2子系统驱动架构 - ioctl 基于linux4.6.3,最后会附上一张ioctl调用总图,分析代码还是要用图来说明,这样更清晰一点,我就是这么分析的,不过平时分析的图很随便,而且很大,所以就不能在这里呈现,我在这里会贴出一个简略图 ioctl详解 进入ioctl都是从cdev-ops-ioctl进入的,一般的驱动cdev都是驱动自己初始化的,在v4l2架构中,cdev都已经初始化完成,不需要驱动开发者来初始化,下面是v4l2的cde
杂谈 最近在研究gradle ,插件化~自己碰到的坑很多.今天先总结一下 以下这三个都研究过,原理都是一样的,区别就在于用哪个更方便. 在这里我会讲述一下,这里面的原理和自己爬的坑,以便大家理解,还有少爬坑~~ 原理是需要懂得~ 不然,你遇到错误不会解决,并且你始终会是初级工程师~ 首先,按照顺序,介绍下目前三种热修复的方式: 1.Nuwa (基于gradle写的脚本,操作起来比较麻烦,需要拷贝和运行命令行~) https://github.com/jasonross/Nuwa 2.andfix (看过~
题记——  难过了,悄悄走一走;         伤心了,默默睡一觉;         优雅不是训练出来的,而是一种阅历;         淡然不是伪装出来的,而是一种沉淀;         时间飞逝,老去的只是我们的容颜;         时间仿佛一颗灵魂,越来越动人; 1、简述:     在多线程的世界中,是那么的神奇 与 高效以及合理; 2、创建线程池实例     官方推荐使用Executors类工厂方法来创建线程池管理,Executors类是官方提供的一个工厂类,里面封装了好多功能不一样的线程池,
书接上回,我们已经了解了一些关于适配的一些相关概念,接下来我们会了解一下,在设置布局时我们应该注意的地方。 尽量不去设定具体的尺寸值。 为了确保布局适应各种尺寸的屏幕,在保证功能实现的前提下,最好不要写死一些尺寸,这样的硬编码,我们最好使用“match_parent”,”wrap_content”,”weight”这些不用指定具体的尺寸值的参数,这样视图就会根据自身需要的空间去充填。这样就可以让布局去适应各种屏幕的尺寸,当屏幕有旋转时也不会受到影响。 这里我们重点说一下“weight”,用过 LiearL

艺术般的波浪点击反馈效果 - 2016-07-24 17:07:27

Material Design之Rippledrawable 使用与简单封装(向下兼容至selector) 前言 Android 5.0问世以来,谷歌所推崇的Material Design得到业界的一致好评,其良好的UI规范与交互确实让界面交互友好和漂亮了不少,Rippledrawable便是其中之一,本博客今天着重讲如何将它运用到我们自己的项目中,并且封装得简单易用。 我们都知道,我们在之前做按钮或者布局的反馈效果,一般都用selector来实现,分别指定按下或正常状态的两种颜色即可,我们点击的效果也本
ubuntu环境 首先确定是否安装了Git管理工具 sudo apt-get install git 我选择SSH方式,比较安全方便,只需一次配置 1- 使用ssh命令连接github.com的SSH服务,登录名为git@github.com(所有GitHub用户共享此SSH用户名)。 wangxiong @Dell :~/Public/GitHubRepository/PaPaPlayer $ ssh - T git @github .com The authenticity of host 'gith
         每次看到iOS的远程消息推送,总是感觉很头大,即便后来项目都做完了,还是觉得摸不着远程推送的脉门,网上介绍的资料虽多,但不是写的太简单了,就是写的太详细了,不能一下抓住要点,今天终于能够抽出点时间,来扒一扒这其中究竟有怎样的奥秘。     根据苹果掌控一切的习惯,消息推送也当然不能例外,不论你在哪里推送,也不论你用什么方式推送,都必须首先把消息发给苹果的消息推送服务器APNs(Apple Push Notification Service),然后再由APNs发给指定的设备,也就是说消息推
Day02 Html、Css实战和WebView实现手机显示网页 1.html与css实战 1.1 程序猿小网页 先来看一下效果图 编程用图如下 实现代码如下 !DOCTYPE htmlhtml head meta charset="utf-8" title/title style #pic{ position: relative; float: left; } #text{ width: 400; height: 200; position: relative; float: left; font-si