FMDB v2.6.2
这是个SQLite的OC封装。SQLite的地址:http://sqlite.org/
FMDB邮件名单
http://groups.google.com/group/fmdb
阅读SQLite的常见问题
http://www.sqlite.org/faq.html
由于FMDB建立在SQLite之上,你想要至上而下读一遍这个页面。当你在这里,确保你标记了SQLite的文档页面:http://www.sqlite.org/docs.html
特约Contributing
关于FMBDB你是不是有什么惊奇的点子呢?你应该考虑联系ccgus,不过你得确定他并没有因为一些理由限定了这个点子。另外,pull是随意的,保持你本地编程的习惯。然而,即使几个礼拜也没有收到来自ccgus的任何讯息也别着急,你可以发个消息问一下发生了什么。
CocoaPods
FMDB支持CocoaPods做依赖管理。
如果需要数据可加密你需要使用FMDB/SQLCipher.FMDB/SQLCipher声明了SQLCipher的依赖,允许FMDB通过“-DSQLITE_HAS_CODEC”标志去编译。
FMDB类参考
http://ccgus.github.io/fmdb/html/index.html
ARC还是手动内存管理
你可以选择两种中任意一种方式,在你的cocoa项目中,FMDB可以在编译时候跟据你的选择编译正确的code。
用法
FMDB中有三个主要的类
1.FMDatabase-代表一个独立的SQLite数据库,执行SQL语句。
2.FMResultSet-代表FMDatebase查询的结果集
3.FMDatabaseQueue-如果你想要在多线程中查询和更新,你应该使用这个类,
创建数据库
FMDatabase通过一个SQLite数据库文件的路径创建。这个路径可以有以下三种样式:
1.一个系统文件路径.硬盘上之前不存在的,如果它不存在,FMDB会为你新建。
2.一个空字符串,一个空数据库在临时文件中创建。在数据库连接关闭的时候,这个数据库会被删除。
3.NULL(空)。一个在内存中的数据库将被创建。在数据库连接关闭的时候,这个数据库会被销毁。
想了解更多关于临时或内存数据库,请阅读sqlite文档:http://www.sqlite.org/inmemorydb.html
eg: FMDatabase *db = [FMDatabase databaseWithpath:@“/tmp/tmp.db”];
打开数据库
和数据库建立连接之前,应该确保它是打开的,在内存不足、禁止开启、创建数据库的时候会打开失败。
if(![db open]){
[db release];
return;
}
执行更新
除了SELECT格式的数据库执行语句都是更新。包括CREATE,UPDATE,INSERT,ALTER,COMMIT,BEGIN,DETACH,DELETE,DROP,END,EXPLAIN,VACUUM,还有replace语句等等。基本上,只要你的SQL语句不是以SELECT开头,都是更新语句。
执行更新返回一个单一BOOL值,返回值为YES代表执行更新成功,返回值为NO表示发生了一些错误。你可以调用-laseErrorMessage和-laseErrorCode方法接收更多讯息。
执行查询
一个SELECt语句是一个查询语句并且通过-executrQuery方法执行。
执行查询成功返回FMResultSet对象,失败返回nil。你应该使用-laseErrorMessage和-laseErrorCode方法确定到底为什么查询失败。
为了循环访问查询结果集,你可以使用while()循环.你也需要从一个纪录到另一条。在FMDB中,最简单的办法是这样:
FMResultSet *s = [db executeQuery:@“SELECT *FROM myTable”];
while ([s next]){
//retrieve values for each record
}
通常你在使用查询结果的返回值前必须先调用-[FMResultSet next],即使你只需要一次,像这样:
FMResultSet *s = [db executeQuery:@“SELECT COUNT(*) FROM myTable”];
if ([s next]) {
int totalCount = [s intForColumnIndex:0];
}
FMResultSet 拥有许多方法去获取适当类型的数据
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnName:
objectForColumnName:
这里的每一个方法都有一个对应的{type}ForColumnIndex:表达式。基于字段在结果集中的位置可以被用来获取数据
,和字段名一一对应。
特别的,你在这里不需要-close一个FMResultSet,直到结果集都被释放或者父数据库关闭了。
关闭(Closing)
当你完成了数据的查询和更新,你应该-close这个FMDatabase的连接让SQLite释放那些操作过程中占用的资源。
[db close];
Transactions(事务)
FMDatabase可以通过调用合适的方法或者执行开始和结束事务型语句开始并提交一个事务。
多条语句和批量添加
你可以使用FMDatabase的executeStatements:withResultBlock:去做一个多条SQL语句在一个字符串中:
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
sql = @"select count(*) as count from bulktest1;"
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[@"count"] integerValue];
XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
return 0;
}];
数据处理
当提供一个SQL语句到FMDB。你应该检查每一个输入值.代替的,你可以使用标准绑定语法:
INSERT INTO myTable VALUES (?, ?, ?, ?)
?符号在SQLite标准中是一个插入的占位符。执行方法全部的接受来自参数的变量值(或者是这些参数的,像NSArray,NSDictionary,或者是va_list),适当对你隐藏。
还有,在OC中使用使用?占位符就像这样:
NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty (\"the famous Irish author\")";
NSDate *date = [NSDate date];
NSString *comment = nil;
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
注意:基本数据类型,像NSInteger这样的变量自负,应该用MNSNumber对象表示,需要用@语法,像上面那样。或者用[NSNumber numberWithInt:identifier]语法也可以。
另外,在SQL中NULL值应该用[NSNull null]插入.举个例子,像上面那个comment自断可能为nil,你可以一使用comment ?: [NSNull null]语法,当comment为不为空是插入这个字符串,为空时插入[NSNull null].
在swift中,你应该使用executeUpdate(values:),不仅仅是swift的简明语法,也同样是swift2的错误抛出句柄。
do {
let identifier = 42
let name = "Liam O'Flaherty (\"the famous Irish author\")"
let date = NSDate()
let comment: String? = nil
try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
} catch {
print("error = \(error)")
}
注意:在swift,你不需要像OC那样基础数字类型的封装,但是如果你想要插入一个可变字符串,你将需要使用这样的语法comment ?? NSNull() (i.e.,如果为nil,使用NSNull,否则使用这个字符串)。
二选一,你可以使用named参数语法。
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
这个参数列表必须以冒号开始,SQLite也支持其它符号,但是内部字典的key是以冒号前缀的,你字典里的key不要包含冒号。
NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
在SQL语句中key应该使用NSString的stringWithFormat方法手动插入对应的值。那么swift的字符串咋办?,使用?占位符向数据库插入值(或者在SELECT语句中使用WHERE从句)
使用FMDatabaseQueue 和线程安全
在多线程使用同一个FMDatabase的单例是一个坏主意。通常为每一个线程创建FMDatabase对象都是OK的,千万不要跨线程使用数据库单例,清楚不要在多线程同时使用。坏的事情终将发生你将会得到一些闪退,或者一个异常,或者是陨石从天而降砸中了你的mac,这些将会被抱怨。
多线程使用的时候不要实力话单例。
用FMDatabaseQueue替代。实例化一个单例FMDatabaseQueue并且在多线程中使用.这个FMDatabaseQueue对象将通过队列管理同步执行来自多线程的命令。这里是如何使用,第一步,创建你的队列。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
然后这样使用它:
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
…
}
}];
简单的多任务包装在一个事务中key这样做:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc…
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];
在swift里这样:
queue.inTransaction { db, rollback in
do {
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])
if whoopsSomethingWrongHappened {
rollback.memory = true
return
}
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [4])
} catch {
rollback.memory = true
print(error)
}
}
FMDatabaseQueue将在序列号的队列(从类名开始)执行块.因此你可以同时调用在多线程的多FMDatabaseQueue的方法,在顺序排到的时候它们将被执行。这个方法查询和更新并不会挤在一起,就不会出问题了。
注意:FMDatabaseQueue的方式是块形势的,因此即使你正在块中,他们也不会跑在其它线程。
通过块自定义sqlite函数
你可以做这个,例如:查看main.m里的 -makeFunctionNamed:
swift
你也可以在Swift项目中使用FMDB
如果是这样,你必须:
1.从FMDB的src文件下拷贝相关的.m和.h文件到你的项目中。
你可以拷贝所有的文件(最简单的方式),或者只拷贝你需要的。最基本的你需要FMDatabase和FMResultSet。FMDatabaseAddition提供一些非常方便好用的方法,因此你可能也需要它。如果你要在多线程任务中使用数据库,FMdatabaseQueue也是非常有用的。如果你选择不要拷贝所有src文件目录下的文件,你还需要更新FMDB.h,这是与你项目的唯一连接。
注意,如果你拷贝了所有文件到你的项目中(推荐做法),你可能想要拖动个人文件到你的项目中,不是文件夹,因为如果你拖动文件夹,你将得不到关于添加桥引头文件(bridging header)的提示(看下一点)
2.如果你被提示创建桥引头文件,你应该创建它。如果你没有收到提示且之前并没有桥引头文件,你应该去添加一个。
更多关于桥引头文件,看swift和oc混编( Swift and Objective-C in the Same Project.)
3.在你的桥引头文件中,添加这一行。
#import "FMDB.h"
4.在try模式下使用executeQuery和executeUpdate的变量表达式带上sql和values参数,就像下面的代码一样,这些executeQuery和executeUpdate的译文都会抛出对应的异常在swift2中。
如果你按照上面的做了,你可以用FMDatabase写swift代码,举例:
let documents = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let fileURL = documents.URLByAppendingPathComponent("test.sqlite")
let database = FMDatabase(path: fileURL.path)
if !database.open() {
print("Unable to open database")
return
}
do {
try database.executeUpdate("create table test(x text, y text, z text)", values: nil)
try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["a", "b", "c"])
try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["e", "f", "g"])
let rs = try database.executeQuery("select x, y, z from test", values: nil)
while rs.next() {
let x = rs.stringForColumn("x")
let y = rs.stringForColumn("y")
let z = rs.stringForColumn("z")
print("x = \(x); y = \(y); z = \(z)")
}
} catch let error as NSError {
print("failed: \(error.localizedDescription)")
}
database.close()
历史
历史和变化总结在GitHub Page的CHANGES_AND_TODO_LIST.txt里可见。
贡献者
FMDB的贡献者名单在Contributors.txt”中
在项目中使用FMDB,你应该有志于成为一个有辨识力的开发者
FMDBMigrationManager,一个FMDBSQLite架构迁移管理系统
https://github.com/layerhq/FMDBMigrationManager
FCModel,一个对操作SQL易取可选择的核心数据模型。
https://github.com/marcoarment/FCModel
FMDB开源的代码风格
空格,不要tab键。方括号,不要打点语法。FMDB的早就在执行方括号格式,一直保持这样的风格。
报告bug
编码时尽量减少bug。你需要让开发者很简单的发现你的bug并且复现。如果有用,装作那个可以有效解决bug的人支持着3个主要项目。为一些开源项目工作,有一个新生的宝宝,通常时候非常非常忙。
我们曾经添加模版函数在main.m(FMDBReportABugFunction)在FMDB发布版本帮你解决。
通过Xcode打开fmdb项目
打开main.m函数修改FMDBReportABugFunction重现。
设置你的表在代码中。
确认你的查询或者是更新
添加一些关于这个bug的断言
然后你可以把这些通过邮件发给FMDB邮件表。或者,你可以纪录这个bug通过githubFMDBbug记录员。
可选的:
解决这个bug,发送附件到邮件表,确保所有其它测试在你的修改运行之后。
支持
这个FMDB支持通道是一个邮件地址列表(上面有),bug提在这里,或者在Stack Overflow。所以就是说,社区和志愿组织提供支持。
FMDB开发是监视。如果FMDB对你有帮助,考虑采购一个应用FM或者告诉你的朋友关于它。
执照
FMDB的许可证在"License.txt”文件中