美文网首页iOS进阶iOS Collection我的iOS开发小屋
高度封装FMDB框架:各用一句代码更新(添加&修改),查

高度封装FMDB框架:各用一句代码更新(添加&修改),查

作者: J_Knight_ | 来源:发表于2016-10-10 04:47 被阅读4423次

    在移动开发中,有时不得不在客户端本地保存一些数据。在iOS端,我们可以使用plist,属性列表等技术来存储数据,而相比而下更高端一点的,我们也可以使用数据库来存储数据。

    有趣的是,很多iOS开发者没有去选择使用苹果自家的Core Data技术来操作数据库,而是选择了FMDB这个第三方框架。

    该框架很好地封装了操作繁琐的SQLite语句,让数据库的操作更加面向对象,而且上手快,门槛低,不用学习数据库的相关知识就可以使用自如。如此优秀流行的框架是值得学习的,于是笔者这两天研究了一下FMDB。研究后,略有所思,将它封装了一下,写了一个Manager类,最后结合了一个Demo演示如何使用这个类。

    该博客分为两个部分:第一个部分讲解笔者封装的这个Manager类;第二部分结合Demo来体现该类的实用性。

    1. 封装FMDB


    1.1 封装类的功能

    首先看一下这个类的大名:SJUserInfoManager

    • SJ:笔者的名字缩写,作为前缀,都懂得。
    • UserInfo:说明这个类用于操作用户信息。
    • Manager:这个类只具有工厂方法,省内存,而且方便使用。

    这个类的功能是:它可以创建一个关于用户信息的表(数据库),可以保存,修改,读取,删除用户信息

    现在几乎每个app都有登录功能,有登录就意味着有用户信息。有的时候,我们需要将一些用户信息存储到本地,方便今后的读取和操作。

    而对于用户信息,几乎永远不变的一项就是用户id,因为用户可以改自己的名字,自己的注册手机号,用户签名等等,然而用户id是唯一一成不变的,后端查找用户信息一般都通过用户id来查找,这不难理解。

    因此,这个封装类基于用户id(user_id),让使用者可以自主添加一项自定义的用户信息(可以是用户名,用户签名,用户性别等等),从而形成一个只具有两个纵行的表(第一个纵行是默认的字段:user_id,第二个纵行是自定义字段,可以是user_name等等)。

    这样一来,我们就可以生成很多基于用户id的表:可以是用户姓名的表,可以是用户出生日期的表,可以是用户手机号的表等等。

    1.2 API介绍

    该封装类的API一共有五个,分别是:

    1. 创建表格
    2. 更新用户信息(添加&修改)
    3. 查询某个用户的信息
    4. 查询全部用户的信息
    5. 删除某个用户的信息

    下面开始一一讲解:

    1. 创建表格

    /*
     ********** Create table with tableName and fieldName **********
     *
     * @param   dataBaseName    tableNameString (表的名字)
     * @param   userInfoField   fieldNameString(自定义字段名)
     *
     * @return  if the table is successfully created
     *
     */
    + (BOOL)createDataBaseWithName:(NSString *)dataBaseName andUserInfoField:(NSString *)userInfoField;
    
    

    这个方法的意图很明显,只要传进去表的名字和自定义字段的名字就能创造一个表。

    • 创建成功,返回YES;
    • 创建失败,返回NO。

    而且这个表有一个默认的字段:user_id。因为通过这个类创建用户信息的表是基于用户id来操作的,前面已有说明。

    2. 更新用户信息(添加&修改)

    /*
     ********** update specific userInfo with specific userID and userInfoField and userInfoValue **********
     *
     * @param   dataBaseName    tableNameString (表的名字)
     * @param   userID          userIDString(用户id的值)
     * @param   userInfoField   fieldNameString(自定义字段名)
     * @param   userInfoValue   userInfoValueString(字段对应的值)
     *
     * @return  the result of updating specific userInfo 
     *
     */
    + (NSString *)updateUserInfoIntoDataBase:(NSString *)dataBaseName withUserID:(NSString *)userID andUserInfoField:(NSString *)userInfoField andUserInfoValue:(NSString *)userInfoValue;
    

    这个方法用于更新用户信息,传入表的名字,用户id,自定义字段名,和自定义字段对应的值。

    • 如果输入的表不存在,则返回相应的错误信息。
    • 如果输入的用户id已经存在,就更新对应的用户信息。
    • 如果输入的用户id不存在,就添加这个用户的信息。

    3. 查询某个用户的信息

    /*
     ********** Query specific userInfoValue with tableName and userID and userInfoField **********
     *
     * @param   dataBaseName    tableNameString (表的名字)
     * @param   userID          userIDString(用户id的值)
     * @param   userInfoField   fieldNameString(自定义字段名)
     *
     * @return  specific userInfoValue (表内某用户id对应的用户信息)
     *
     */
    + (NSString *)queryUserInfoInDataBase:(NSString *)dataBaseName WithUserID:(NSString *)userId andUserInfoField:(NSString *)userInfoField;
    
    

    该方法用于查询某个用户的信息,传入表的名字,用户id和自定义字段名。

    • 如果输入的表不存在,则返回相应的错误信息。
    • 如果输入的用户id存在,则返回对应的信息。
    • 如果输入的用户id不存在,则返回相应的错误信息。

    4. 查询全部用户的信息

    /*
     ********** Query all userInfos in this table with userInfoField **********
     *
     * @param   dataBaseName    tableNameString (表的名字)
     * @param   userInfoField   fieldNameString(自定义字段名)
     *
     * @return  all the userInfos in this table (表内所有的用户信息)
     *
     */
    + (NSDictionary *)queryUserInfosInDataBase:(NSString *)dataBaseName andUserInfoField:(NSString *)userInfoField;
    
    

    该方法用户获取某个表内的所有用户信息,传入表的名字和自定义字段名即可。
    返回的字典里包含一个数组,数组元素为表的每一行的信息。每一行的信息是一个字典:

    • 其中一个key为默认的字段名:user_id,它的值为对应的user_id的值。
    • 另一个key为添加的自定义字段名,它对应的值为该字段对应的值。

    5. 删除某个用户的信息

    /*
     ********** Delete specific userInfo with specific userID **********
     *
     * @param   dataBaseName    tableNameString (表的名字)
     * @param   userId          userIDString(用户id的值)
     *
     * @return  the result of deleting specific userInfo (删除的结果)
     *
     */
    + (NSString *)deleteUserInfoInDataBase:(NSString *)dataBaseName WithUserID:(NSString *)userId;
    

    该方法用于删除表里的某个用户信息,只要传入表的名字和用户id即可,可以删除表中对应的一整行信息。同样地,如果输入的表不存在,则返回相应的错误信息。

    如果觉得有点抽象的话,可以看下面这个Demo,可以看到该封装类的具体使用方法。

    2. Demo演示


    在这个Demo中,我们要在表里添加的自定义字段名(fieldNameString)为:“user_name”。也就是说这个表将保存用户id和用户名信息。

    2.1 需求

    1. 在插入板块中插入用户信息(用户id和用户名)。
    2. 在查询板块中,根据输入的用户id,可以显示对应的用户名。如果没有对应的用户id,则显示“没有对应id”。
    3. 在删除板块中,根据输入的用户id,可以删除对应的用户信息(包括用户id和用户名,也就是删除了表的一整行)。如果没有对应的用户id,则显示“没有对应id”。
    4. 点击导航栏右侧的按钮,进入“用户信息列表页”。在这一页显示当前表里的全部用户的信息(在cell的textLabel里显示用户名;在cell的detailTextLabel里显示用户id)。

    2.2 效果图

    左图:数据库操作页面(插入,查询,删除板块)| 右图:数据库全部用户信息 本地沙盒中sqlite表文件内容

    2.3 代码讲解

    1. 最先导入这个封装类和FMDB框架:

    #import "SJUserInfoManager.h"
    

    2. 因为表的名字和自定义字段是固定的,所以就以宏来定义了:

    #define DATABASENAME  @"userInfoTable" //表的名字
    #define USERINFOFIELD @"user_name"     //自定义字段的名字
    

    3. 添加几个输入框的属性,获取输入框的内容;和查询结果显示Label:

    @property (strong, nonatomic) IBOutlet UITextField *insertUserIdTextfield;       //插入输入框:输入用户id
    @property (strong, nonatomic) IBOutlet UITextField *insertUserInfoValueTextfiled;//插入收入框:输入用户名
    
    @property (strong, nonatomic) IBOutlet UITextField *queryUserIdTextfield;        //查询输入框:输入用户id
    @property (strong, nonatomic) IBOutlet UILabel *queryUserInfoValueLabel;         //查询结果显示Label:显示用户id对应的用户名
    
    @property (strong, nonatomic) IBOutlet UITextField *deleteUserIdTextField;       //删除输入框:输入用户id
    

    4. 下面看一下封装的增改&查&删的代码:

    //插入用户信息
    - (IBAction)insertAction:(id)sender {
       
        NSString *result = @"";
        
        if (self.insertUserInfoValueTextfiled.text.length == 0) {
            
             result = @"Please Input UserID!";//没有输入用户id就点击了插入按钮
            
        }else{
            
             result = [SJUserInfoManager updateUserInfoIntoDataBase:DATABASENAME withUserID:self.insertUserIdTextfield.text andUserInfoField:USERINFOFIELD andUserInfoValue:self.insertUserInfoValueTextfiled.text];
        }
       
       [self showAlertWithTitle:result];
        
    }
    
    //查询用户信息
    - (IBAction)queryUserInfoValue:(UIButton *)sender {
        
        NSString *result = @"";
        
        if (self.queryUserIdTextfield.text.length == 0) {
            
            result = @"Please Input UserID!";//没有输入用户id就点击了查询按钮
            self.queryUserInfoValueLabel.text = @"";//重置查询输入框
            
        }else{
            
            result =  [SJUserInfoManager queryUserInfoInDataBase:DATABASENAME WithUserID:self.queryUserIdTextfield.text andUserInfoField:USERINFOFIELD];
            self.queryUserInfoValueLabel.text = result;
            [self showAlertWithTitle:result];
            
        }
        
        [self showAlertWithTitle:result];
    }
    
    //删除用户信息
    - (IBAction)deleteUserInfoWithUserID:(UIButton *)sender {
        
        NSString *result = @"";
        
        if (self.deleteUserIdTextField.text.length == 0) {
            
            result = @"Please Input UserID!";
            
        }else{
            
            result =  [SJUserInfoManager deleteUserInfoInDataBase:DATABASENAME WithUserID:self.deleteUserIdTextField.text];
        }
        
        [self showAlertWithTitle:result];
        
    }
    

    其实不难看出,除了处理错误信息的代码以外,数据库的操作代码是非常简洁的:都是一行结束,而且返回一个结果字符串或者布尔值。

    5. 在进入第二页之前,需要查询表内所有的用户信息并传递给第二个页面的VC:

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.destinationViewController isKindOfClass:[DataListTableViewController class]]) {
            if ([segue.identifier isEqualToString:@"userInfosList"]) {
                
                NSDictionary *userInfosDict = [SJUserInfoManager queryUserInfosInDataBase:DATABASENAME andUserInfoField:USERINFOFIELD];
                DataListTableViewController *dataListVc = (DataListTableViewController *)segue.destinationViewController;
                dataListVc.userInfosDict = userInfosDict;
            }
        }    
    }
    

    6. 在第二页的viewDidLoad方法里获取用户信息列表,并刷新表格将其显示出来:

    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        NSString *alertTitle = [self.userInfosDict objectForKey:@"result"];
        [self showAlertWithTitle:alertTitle];
        
        NSArray *userInfosArray = [self.userInfosDict objectForKey:@"userInfosArray"];
        if ([userInfosArray count] != 0) {
             self.userInfosArray = userInfosArray;
            [self.tableView reloadData];
        }
        
    }
    

    想知道Demo是如何实现的么?

    Demo传送门:SJUserInfoManager - GitHub

    如果可以给星星,或者给笔者提意见,那就再好不过啦~

    最后的话


    说来惭愧,笔者封装的这个类的功能还是很有局限性的:

    1. 操作表格前必须创建一次表格(一次就可以)。
    2. 只支持NSString类型的值。
    3. 除user_id字段以外只支持一个自定义字段。
    4. 不同的自定义字段必须对应不同的表格名,也就是说不同的自定义字段不能对应同一个表格名。
    5. 数据库操作API的返回值还不够完善,最好以枚举类型返回,有待改进。
    6. 没有使用FMDB的队列API。

    虽然这个封装很简单,但是笔者始终赞同学习的最终目的在于应用和创造这句话。如果只是用眼睛看FMDB框架的API或是复制粘贴一些别人写好的FMDB的应用代码,那么其实是意义不大的。

    如果在学习后,可以融会贯通,将学到的知识可以按照自己的意图加以改进和运用的话,那么相对于“搬运工”式学习来说,显然收获更大。

    这是笔者第一个开源项目,虽然简单,算上优化和改bug只写了大概3个小时,但是毕竟是开源的第一步,对自己的鼓励还是蛮大的,以后还会封装优化更多的库,加油~

    本文已同步到个人博客:传送门,欢迎常来^^

    本文已在版权印备案,如需转载请访问版权印。48422928

    获取授权

    相关文章

      网友评论

      • Neo_joke:其实对SQL进行了进一步抽象,核心就是将创建表的行为进行了封装,其实沿着这个思路换个方向会更好,比如说将表对象化,ORM是比较方便的
        J_Knight_:@Neo_joke 嗯嗯 学习了 有空研究一下哈~
      • gitKong:我之前也封装了一个,不过我的功能貌似强大点:stuck_out_tongue_winking_eye: http://www.jianshu.com/p/17defd564844 指教指教,也有些没完善
        J_Knight_:@gitKong 嗯嗯 我这个功能很单一,当时没想太多,我去学习学习:smile: 谢谢分享~
      • 70437fc74756:可以参考一下LKDB对FMDB的二次封装
        J_Knight_:@花样年华_ 好的好的 谢谢
      • 6号特工:我来占个座。
        J_Knight_:@小李飞书 哈哈 欢迎
      • 大Z哥:早就封装过了,用了好几个项目了,作者入行时间不长啊
        J_Knight_:@zerver 嗯嗯 还没到一年 这个就是练练手:smile:
      • 困惑困惑困惑:遵义咯
        J_Knight_:@困惑困惑困惑 ??
      • Joe_lisa:怎么用呢?将FMDB 和manger,拖到代码中吗
        J_Knight_:@Joe_lisa 恩恩 是的,哎呀 我在这里好像没有写,只在GitHub上写了
      • 6988cf2af3a0:支持。不过我觉得数据库这块封装最好还是orm。不如想法儿做一个?github上有个 https://github.com/kohkimakimoto/FMDBx 我觉得不错。
        J_Knight_:@justindigix 不懂什么事orm,研究一下。受教了! :+1:
      • 阿拉斯加的狗:叼叼叼
        J_Knight_:@阿拉斯加的狗 这不是狗哥么! 多谢狗哥夸奖 :smile:
      • 卟师:我能转载分享吗?我会标注上作者和出处的
        卟师:@Knight_SJ 我会放在微信公众号:iOS面向编码
        J_Knight_:@卟师 谢谢了
        J_Knight_:@卟师 可以的 转载后把链接告诉我:smile:
      • 朋友有朋:有前途好好干
        J_Knight_:@朋友有朋 谢谢 :smiley: 你也加油
      • 谈Xx:看了下sql语句的拼接地方, 该用tablename的时候是不是用databasename不太合适
        谈Xx:@Knight_SJ 我的意思是 你的语句拼接时, 使用的变量名,在用到表名的时候,定义的变量是databasename
        J_Knight_:@谈Xx 有什么更好的建议么?:smile:
        J_Knight_:@谈Xx 我是把这两个名字统一使用了 因为这个数据库里只有一张表。
      • Tr2e:马一个
        J_Knight_:@Tr2e :smiley:
      • 五阿哥永琪:楼主加油
        J_Knight_:@五阿哥永琪 谢谢哈~ 你也加油
      • 会跳舞的狮子:棒棒哒 小厨 健身 有相同的爱好
        J_Knight_:@会跳舞的狮子 哈哈 好的好的
        会跳舞的狮子:@Knight_SJ ok 技术 健身 厨艺都多交流
        J_Knight_:@会跳舞的狮子 哈哈 已互粉~ 多交流哈~
      • iOS俱哥:描述一个封装的控件比较详细,能让人有耐心的读完,记录手法值得我学习和借鉴!:+1:
        J_Knight_:@iOS俱哥 谢谢!能让人容易理解,容易看下去是我始终追求的:smiley:
      • 陪我嘟嘟:很好
        J_Knight_:@陪我嘟嘟 谢谢 :smiley:
      • HYD枫:github 都是英文哦 ,看来楼主英文水平可以啊!
        J_Knight_:@HYD枫 昨天刚上传的,可能写的比较急了
        J_Knight_:@HYD枫 没有了其实,刚才看到了语法错误,得改了。有点囧
      • HYD枫:不错啊 ,
        J_Knight_:@HYD枫 谢谢 :smile:
      • wbtuxi:加油看好你哦 :smile:
        J_Knight_:@似水流年灬流年逝水 谢谢啊 :smile:
      • Sephiroth_Ma:支持
        J_Knight_:@寒宇少 谢谢 :smile:

      本文标题:高度封装FMDB框架:各用一句代码更新(添加&修改),查

      本文链接:https://www.haomeiwen.com/subject/dbemyttx.html