iOS中原生的SQLite API在使用上相当不友好,在使用时,非常不便。于是,就出现了一系列将SQLite API进行封装的库,例如FMDB,PlausibleDatabase等.
安装
使用CocoaPods来安装FMDB
CocoaPods是Swift和Objective-C Cocoa项目的依赖管理器。它拥有4万多个库,用于超过280万个应用程序。CocoaPods可以帮助您优雅地扩展您的项目。
如果你没有创建CocoaPods工程,在工程目录下使用
$ pod init
来初始化项目,在生成的Podfile文件中添加FMDB:
target 'MyApp' do
pod 'FMDB'
# pod 'FMDB/FTS' # FMDB with FTS
# pod 'FMDB/standalone' # FMDB with latest SQLite amalgamation source
# pod 'FMDB/standalone/FTS' # FMDB with latest SQLite amalgamation source and FTS
# pod 'FMDB/SQLCipher' # FMDB with SQLCipher
end
然后执行
$ pod install
然后使用新生成的*.xcworkspace
工程,而不是*.xcodeproj
。
用法
在FMDB中有三个主要的类;
-
FMDataase
-表示一个SQLite数据库.用于执行SQLite语句 -
FMResultSet
-表示数据库的查询结果 -
FMDatabaseQueue
-在多个线程来执行查询和更新时会使用这个类
创建数据库
需要制定一个文件路径(path)来创建一个给予SQLite的FMDatabase
,有以下三种方式可以指定文件路径:
- 指定一个文件路径,如果文件不存在,则自动创建
- 指定路径为一个空字符串(
@""
),使用该方式会创建一个临时数据库文件,当FMDatabase
被关闭的时候,临时数据库文件自动被删除 -
NULL
会在内存中创建一个临时数据库,在FMDatabase
被关闭的时候自动删除
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
打开数据库
在与数据库进行交互之前,我们需要打开数据库,如果没有足够的资源或权限打开和/或创建数据库,则打开失败,当打开失败的时候,把db
置为nil
.
if(![db open]){
db = nil;
return ;
}
执行更新
除了SELECT
操作外,SQLite的所有命令都使用executeUpdate
消息来执行.包括CREATE
, UPDATE
, INSERT
, ALTER
, COMMIT
, BEGIN
, DETACH
, DELETE
, DROP
, END
, EXPLAIN
, VACUUM
,REPLACE
等,只要你不是执行SELECT
语句,就是更新数据库操作.
BOOL succ = [db executeUpdate:sql]; //sql是SQLite命令语句字符串
执行更新会返回一个布尔值。返回值YES意味着更新成功执行,返回值NO意味着遇到了一些错误。可以调用lastErrorMessage
和lastErrorCode
方法来检索更多的信息。
执行查询
一个SELECT
语句是一个查询,并通过其中一种-executeQuery...
方法执行。
如果成功执行查询则返回一个FMResultSet对象,失败返回nil
。可以使用lastErrorMessage
和lastErrorCode
方法来确定查询失败的原因。
为了迭代你的查询的结果,可以用一个while()
循环,在循环中使用next
方法来不断取出结果.
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
即使你只希望查询一个值也需要使用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:
UTF8StringForColumn:
-
objectForColumn:
例如,取出一个NSString
的name
数据
NSString *name = [s stringForColumn:@"name"];
每个方法还有一个对应的{type}ForColumnIndex:
变量,用来根据结果中列的位置来检索数据,而不是列名。
通常情况下没有必要手动关闭FMResultSet
对象,因为出现这种情况可以当结果集被释放,或者数据库被关闭自动关闭.
关闭数据库
当你完成对数据库的查询和更新时,你应该close
关闭对FMDatabase
连接,这样SQLite将释放资源。
[db close];
事务(Transactions)
FMDatabase
可以使用begin
和commit
来包裹一个事务
多个语句的执行
FMDatabase
可以一次执行多个语句和用block
进行操作
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;
}];
数据
当执行在FMDB
中执行SQL语句,为了数据库的安全,需要使用数据绑定语法.
INSERT INTO myTable VLUEA (?,?,?,?)
?
字符被SQLite识别为要插入的值的占位符。
NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty ";
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
),应该作为一个NSNumber
对象,通过一个语法糖@()
来完成NSInteger
到NSNumber
的转变
同样的SQL中的NULL
值应该使用[NSNull null]
来代替,如上面实例代码中的comment
,使用comment ?:[NSNull null]
来代替nil
.
或者,也可以使用命名参数语法,当传入一个字典类型的时候尤其适用:
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
参数必须以:
开头
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]);
}
关键是不应该使用NSString
方法stringWithFormat
手动将值插入到SQL语句本身。
使用FMDatabaseQueue和线程安全
不要在多个线程中同时使用一个FMDatabase
对象。当然每个线程创建一个FMDatabase
对象总是可以的.只是不要在线程之间共享一个实例,而且绝对不能同时在多个线程之间共享。这样可能导致脏数据或者写入异常.
所以不要实例化一个FMDatabase
对象并在多个线程中使用它。
正确的做法是,实例化一个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]) {
…
}
}];
一种简单的事务的处理方法:
[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 ...
}];
注意:对FMDatabaseQueue
方法的调用是阻塞的,所以你传递的^block
,不会在另一个线程上运行.
网友评论