题记:
时间飞快,转眼又是两个月,这期间又经历了一个小的app,之后接触了一个新的内容IM,当然最近一段时间内,这个也是一直在做的;
其实更新的blog的想法已有好久,一直没确定好内容,正好IM SDK中涉及到conversation和message的本地化处理,就选了这个主题;
实话讲,在写这个项目之前,我对数据库真的不熟,甚至sql语句都记不清了(我真的学过^_^),为此我买了一本O REILY的《SQL学习指南》读了一遍,书很好推荐给大家;
最终,在CoreData和FMDB之间,我选择了使用FMDB进行本地数据库的操作;
客户端的数据持久化方案:
常用的主要是NSUserDefault和File,Keychain的方式用的不多,也算上,但这些都是处理简单的数据存储,比如我对app登录之后的用户信息存储多数就是通过NSUserDefault进行的;
file的方式每次操作都涉及到文件的读取,稍麻烦些;
对于复杂的数据结构,还是要用到数据库,iOS提供的CoreData实际上是database数据存储和model对象之间转换的一种机制;
SQLite:
sqlite是移动端的轻量级关系数据库解决方案,应用范围也很广;数据库的知识范围很广,简单用也简单,用的溜得也有,比如微信,推荐读下《微信iOS SQLite源码优化实践》;
大部分人所熟知的CoreData和FMDB所操作的数据库,内核都是基于sqlite的;原生是支持SQLite的,只不过API并不友好(道听途说,真没亲眼见过……),开源社区中对API进行封装的库FMDB做的很好,也最常用;
FMDB:
FMDB是对iOS相关SQLite的API进行封装的第三方库,使用者只需调用该框架的API就能很方便的进行本地数据库的操作;
FMDB操作数据库并不消耗太多的性能,而且提供了线程不安全的解决方案;
将FMDB copy到我们的项目,我们可以看到他的目录结构:
核心的三个类:
1)FMDatabase:一个FMDatabase对象就代表一个单独的SQLite数据库;
2)FMResultSet:使用FMDB进行查询得到的结果集合;
3)FMDatabaseQueue:用于解决线程不安全的类,避免每个线程创建一个数据库;
使用:
FMDB提供了创建数据库的接口,我们可以使用
+ (instancetype)databaseWithPath:(NSString*)inPath;
进行数据库的创建,inPath是我们指定的一个沙河路径;
创建表的话,直接执行创建表的sql语句即可;
在实际项目的使用中,我的做法是在资源路径下事先创建好db.sqlite数据库,同时表结构也是事先创建好的,在调用创建数据库时,将该数据库从资源路径copy到沙河路径即可;
[fileManager copyItemAtPath:srcPath toPath:verFilePath error:&error]
在操作数据库之前需要先打开数据库open,通常在执行完相关操作时候,需要调用close方法来关闭数据库;
对于数据库的操作,除了查询以外,其他的都可以理解为更新:
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (BOOL)executeUpdate:(NSString*)sql, ...
执行相应的sql语句即可,先看段示例代码:
FMResultSet *rs_message = [fmdb executeQuery:@"SELECT * FROM message WHERE conversation=? AND username=?;",conversationId,[_sharedManager getUserId]];
while ([rs_message next]){
result = [[HQCMessageModel alloc]init];
result.msgid = [rs_message stringForColumn:@"msgid"];
result.msgfrom = [rs_message stringForColumn:@"msgfrom"];
result.msgto = [rs_message stringForColumn:@"msgto"];
result.msgtype = [NSNumber numberWithInt:[rs_message intForColumn:@"msgtype"]];
result.msgbody = [rs_message stringForColumn:@"msgbody"];
result.conversation = [rs_message stringForColumn:@"conversation"];
result.msgatt = [rs_message stringForColumn:@"msgatt"];
result.atttype = [NSNumber numberWithInt:[rs_message intForColumn:@"atttype"]];
result.msgtime = [NSNumber numberWithLongLong:(long long)[rs_message intForColumn:@"msgtime"]];
result.msgdirection = [NSNumber numberWithInt:[rs_message intForColumn:@"msgdirection"]];
result.isdelivered = [NSNumber numberWithInt:[rs_message intForColumn:@"isdelivered"]];
result.status = [NSNumber numberWithInt:[rs_message intForColumn:@"status"]];
result.servertime = [NSNumber numberWithLongLong:(long long)[rs_message intForColumn:@"servertime"]];
result.username = [rs_message stringForColumn:@"username"];
}
[rs_message close];
这是我从数据库查询message时的一段代码,我们发现,对于rs_message结果集,需要调用next方法,获取相应的查询记录;
结尾的结果集关闭,可以不调用,因为当相关数据库关闭时,该结果集也会自动关闭;
我们的message model记录的字段类型各异,FMDB也为此提供了多个方法来获取不同类型的数据,示例代码中只用了几种,其他的大家可以看下具体的API;
上面的sql语句中,我们按照标准的SQL语句,用?表示了执行语句的参数,这里需要注意:
该参数对应的数据必须为NSObject的子类,对于基本数据类型,需要进行类型装换;
FMDB使用FMDatabaseQueue来保证线程安全,再看一段示例代码:
FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath:filePath];
[queue inDatabase:^(FMDatabase *fmdb) {
[fmdb open];
if (![fmdb tableExists:@"message"]) {
result = nil;
[fmdb close];
return;
}
[fmdb setShouldCacheStatements:YES];
FMResultSet *rs_message = [fmdb executeQuery:@"SELECT * FROM message WHERE msgid=? AND username=?;",messageId,[_sharedManager getUserId]];
这只是一段代码片段,但我们可以清楚的知道FMDatabaseQueue的使用:
首先用数据库文件地址来初始化,在将一个闭包(block)传入,在闭包中完成数据库的操作;
事物(Transaction)是并发控制的基本单位,不可分割,简单来说事物是一种机制,用以维护数据库的完整性,大家可以参看一下《数据库事物 ios FMDB》这篇文章;
需要使用数据库事物的一般是更新和删除等操作;
对于FMDB的事务处理,我们可以参见如下示例代码:
BOOL isSuccess = NO;
[fmdb beginTransaction];
@try {//事务,有异常则回滚
BOOL rs;
rs = [fmdb executeUpdate:@"UPDATE conversation SET unreadcount=?,timestamp=? WHERE converid=? AND username=?;",
conversation.unreadcount,
date,
conversation.converid,
[_sharedManager getUserId]
];
if (rs) {
result++;
}else{
result = 0;
}
}
@catch (NSException *exception) {
isSuccess = NO;
[fmdb rollback];
}
@finally {
isSuccess = YES;
[fmdb commit];
}
以上这些就是FMDB对数据库的基本操作,在实现时可以统一放到一个DBManager类中,统一提供接口;
FMDB对系统API的封装,使对数据库的操作变得很简单;
总结:
对于客户端的开发,数据库的使用是基本的技能之一,之前的项目都是直接联网请求数据展示的,对于数据本地化并没有要求,但并不代表它不重要,相反当业务要求的数据结构较复杂时,数据库的作用就得以凸显,适当的减少了网络交互,同时也能增加app的流畅性和体验,比如,在实现我们的IM SDK时,消息、会话等数据就一定要进行本地处理;
我们的IM SDK是为解决公司内部诸产品线对即时通信的技术需求而实现的,目前还在开发,在这个过程中,体会到了自己的不足,也学习了很多,有机会慢慢分享给大家;
感谢新一阶段的自己(也是为了提醒自己),使这篇文章成为可能,谢谢!