GRDBObjc
为 GRDB.swift 提供与 FMDB 兼容的绑定。
GRDBObjc 帮助使用 SQLite 的 Objective-C 应用程序在最小成本下用 GRDB 替换 FMDB。
最新版本:2019年5月26日 • 版本 0.8 • 发行说明
要求:iOS 9.0+ / macOS 10.9+ / watchOS 2.0+ • Swift 4.2+ / Xcode 10.0+
关注 Twitter 上的 @groue 以获取发布通知和使用技巧。
安装 • 演示 • FMDB 兼容性图表
用一点点 Swift 取悦自己
事实上,我们这些开发者维护和开发 Objective-C 应用程序,希望能够向它们中注入更多 Swift。
从头开始重写一个应用程序通常不是一个合理的选择,尽管Swift的吸引力非常强。我们保留了代表多年开发的遗产Objective-C代码,并将Swift隔离在可以轻松插入到Objective-C主体中的一些功能上。
在这样一个包含Objective-C主干的应用中,一些Swift代码片段有时会受到它们外来基础的限制。也许是导入的Objective-C代码看起来不够Swift化。也许我们梦想着能提供更优解决方案的Swift替代品。
Swift世界中的FMDB
在我们的公司在 Pierlis 认为 FMDB 十分急需。FMDB做了一项伟大的工作,但GRDB做得更好。
2015年,GRDB是一个在FMDB上高度启发的内部项目。三年和四个Swift版本之后,这个库已经达到了API稳定性,并提供了专注于应用程序开发的一套强大的工具。基础相同:GRDB与它令人敬畏的先驱一样精通SQL。它提供了相同的强大并发保证。然而,GRBD增加了独一无二的Swift风味,以及数据库观察和记录类型等FMDB中看不到的特性。
例如,让我们比较两个加载应用程序模型数组的等效代码片段。使用GRDB,它提供
struct Player: FetchableRecord {
...
}
let players: [Player] = try dbQueue.read { db in
try Player.fetchAll(db, sql: "SELECT * FROM player")
}
// use players array
FMDB创作了另一种类型的诗歌
struct Player {
...
init(dictionary: [AnyHashable: Any]) { ... }
}
var queryError: Error? = nil
var players: [Player] = []
dbQueue.inDatabase { db in
do {
let resultSet = try db.executeQuery("SELECT * FROM player", values: nil)
while resultSet.next() {
let player = Player(dictionary: resultSet.resultDictionary!)
players.append(player)
}
} catch {
queryError = error
}
}
if let queryError = queryError {
throw queryError
}
// use players array
你可能认为:“我从未像那样使用FMDB!”。确实,使用FMDB进行错误处理既难以阅读又难以编写,而且几乎没有人这么做。然而,正是这种健壮性允许你的应用程序在锁定设备上安全运行,例如。GRDB简洁,不会错过任何错误,而且比FMDB运行上述代码 要快得多。
GRDB的进一步应用
数据库观察 是一个现成的GRDB功能,它将为您节省开发其顶部FMDB上所需的小时数。
例如,GRDB附带高级工具,如 FetchedRecordsController,以及伴随库 RxGRDB,让您能够以响应式方式观察数据库变化。由于观察发生在SQLite级别,即使面对原始SQL更新、外键级联,甚至SQL触发器,也不会被欺骗。不要错过任何一次提交
let request = SQLRequest<Player>(
sql: "SELECT * FROM players ORDER BY score DESC LIMIT 10")
// Observe request changes with FetchedRecordsController:
let controller = FetchedRecordsController(dbQueue, request: request)
controller.trackChanges {
let players = $0.fetchedRecords // [Player]
print("Players have changed")
}
try controller.performFetch()
// Observe request changes in the reactive way:
request.rx.fetchAll(in: dbQueue)
.subscribe(onNext: { players: [Player] in
print("Players have changed")
})
GRDBObjc是GRDB与您的目标为FMDB的Objective-C代码之间的粘合剂
不能简单地在一个应用程序中同时安装FMDB和GRDB,同时Objective-C代码针对FMDB,而Swift新代码使用GRDB。这不会很好地工作,因为SQLite不允许两个不同的连接同时向数据库写入。相反,当Objective-C线程和Swift线程并发修改数据库时,会失败并出现SQLITE_BUSY错误。想想当用户正在编辑某个值时完成的下载操作。
欢迎GRDBObjc。通常,你所要做的就是移除FMDB,安装GRDB和GRDBObjc,并替换#import
指令
-#import <fmdb/FMDB.h>
+#import <GRDBObjc/GRDBObjc.h>
对于您大部分针对FMDB的Objective-C代码来说,这已经足够在GRDB和GRDBObjc之上编译。当然,魔鬼在细节中,以下是一些详细的兼容性图表。
FMDatabaseQueue
,FMResultSet
等类现在由GRDB支持。从Objective-C初始化的数据库队列可在Swift中使用,反之亦然。请参阅以下安装程序中的详细说明。
安装
强制警告:GRDBObjc软件仍然非常年轻。它经过测试,但尚未为很多实际应用提供燃料。
熟悉Swift文档中的“同一项目中使用Swift和Objective-C”章节。
查看FMDB兼容性图表。
如果需要的一些API缺失,或者你发现了一个错误或令人惊讶的行为,请准备好提出拉取请求。
可以使用Cocoapods安装GRDObjc
-
请确保您的应用程序目标使用自动引用计数编译Objective-C:
CLANG_ENABLE_OBJC_ARC = YES
。 -
在应用程序目标中指定构建设置:
SWIFT_VERSION = 4.0
。 -
在Podfile中将FMDB替换为GRDBObjc
-pod 'FMDB' +pod 'GRDBObjc'
-
运行
pod install
。 -
替换Objective-C导入指令
-#import <fmdb/FMDB.h> +#import <GRDBObjc/GRDBObjc.h>
-
运行并测试您的应用程序:确保您的Objective-C代码能很好地处理GRDBObjc。
-
通过桥接头将Objective-C的
FMDatabaseQueue
公开给Swift不要在Swift中使用FMDB API:这不是这个库的目标!相反,从
FMDatabaseQueue
中提取真正的GRDBDatabaseQueue
,并确保Swift代码仅使用GRDBimport GRDB import GRDBObjc // Some FMDatabaseQueue exposed to Swift via the bridging header: let fmdbQueue: FMDatabaseQueue = ... // The genuine GRDB's DatabaseQueue: let dbQueue: DatabaseQueue = fmdbQueue.dbQueue
查看演示应用中的示例设置。
演示
演示应用程序使用与FMDB兼容的API在Objective-C中设置数据库,并使用GRDB从Swift中使用数据库。
要运行此演示应用程序
- 下载此存储库的副本。
- 从
Tests/CocoaPods/TestApp
目录中运行pod install
。 - 在Xcode 9中打开
Tests/CocoaPods/TestApp/TestApp.xcworkspace
。 - 运行应用程序。
- Objective-C和Swift之间的桥接发生在DataStore类和桥接头中。
FMDB兼容性图表
GRDBObjc针对FMDB 2.7.2实现了兼容性。
GRDB和FMDB的行为通常完全相同。当有差异时,GRDBObjc优先考虑FMDB的兼容性而不是GRDB的原生行为。
一些FMDB功能对于GRDBObjc不在范畴之内。例如,GRDBObjc要求通过线程安全的FMDatabaseQueue访问数据库:您将无法创建裸FMDatabase实例。
还有一些其他FMDB功能尚未移植到GRDBObjc。如果您需要的某些API缺失,请提交pull请求。
一般兼容性警告
NSDate:默认情况下,FMDB以数字形式的Unix时间戳存储和读取日期。GRDBObjc出于兼容性考虑也这样做。但是,GRDB将日期存储为YYYY-MM-DD HH:MM:SS.SSS
格式的字符串。它可以原生地从Unix时间戳加载日期。
FMDatabaseQueue
-
完整兼容性可用
@property (atomic, retain, nullable) NSString *path; + (instancetype)databaseQueueWithPath:(NSString * _Nullable)aPath; - (instancetype)initWithPath:(NSString * _Nullable)aPath;
-
带警告的兼容性可用
// FMDB lets you escape a FMDatabase connection from its database // queue's protected blocks. You shouldn't do that, but it's // possible. With GRDBObjc, it is a programmer error, with undefined // consequences, to do so. Don't use an FMDatabase connection // outside of a protected block. - (void)inDatabase:(__attribute__((noescape)) void (^)(FMDatabase *db))block; - (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block; - (void)inDeferredTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
FMDatabase
-
完整兼容性可用
// Properties @property (nonatomic, readonly) void *sqliteHandle; @property (nonatomic, readonly) int64_t lastInsertRowId; @property (nonatomic, readonly) int changes; /// Retrieving error codes @property (atomic, assign) BOOL logsErrors; @property (atomic, assign) BOOL crashOnErrors; - (NSError *)lastError; // Perform updates - (BOOL)executeStatements:(NSString*)sql; // Transactions - (BOOL)beginTransaction; - (BOOL)beginDeferredTransaction; - (BOOL)commit; - (BOOL)rollback; // Save points - (BOOL)startSavePointWithName:(NSString*)name error:(NSError * _Nullable *)outErr; - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError * _Nullable *)outErr; - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError * _Nullable *)outErr; - (NSError * _Nullable)inSavePoint:(__attribute__((noescape)) void (^)(BOOL *rollback))block; // Date formatter + (NSDateFormatter *)storeableDateFormat:(NSString *)format; - (BOOL)hasDateFormatter; - (void)setDateFormat:(NSDateFormatter *)format; - (NSDate * _Nullable)dateFromString:(NSString *)s; - (NSString *)stringFromDate:(NSDate *)date;
-
带警告的兼容性可用
// This property reflects actual SQLite state instead of relying on // the balance of beginTransaction/commit/rollback methods: // // - Some transaction errors have FMDB return true when GRDBObjc // returns false. // // - Opening a transaction with a savepoint method has FMDB return // false when GRDBObjc returns true. @property (nonatomic, readonly) BOOL isInTransaction; // When an NSDecimalNumber parameter contains a value that can be // exactly represented as int64_t, GRDBObjc presents it to SQLite as // an integer. FMDB presents all decimal numbers as doubles. // // When an NSNumber parameter contains an unsigned 64-bit integer // higher than the maximum signed 64-bit integer, GRDBObjc crashes // with a fatal error, when FMDB stores a negative value. - (BOOL)executeUpdate:(NSString * _Nonnull)sql, ...; - (BOOL)executeUpdate:(NSString * _Nonnull)sql withErrorAndBindings:(NSError * _Nullable * _Nullable)outErr, ...; - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; - (BOOL)executeUpdate:(NSString*)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error; - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments; - (FMResultSet * _Nullable)executeQuery:(NSString*)sql, ...; - (FMResultSet * _Nullable)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; - (FMResultSet * _Nullable)executeQuery:(NSString *)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error; - (FMResultSet * _Nullable)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary * _Nullable)arguments;
FMResultSet
-
完整兼容性可用
@property (nonatomic, readonly, nullable) NSDictionary *resultDictionary; - (void)close; - (BOOL)next; - (BOOL)nextWithError:(NSError * _Nullable *)outErr; - (int)columnIndexForName:(NSString*)columnName;
-
带警告的兼容性可用
// GRDBObjc crashes with a fatal error when those methods are called // after the result set has been exhausted, closed, or had an error. // FMDB would always return a value. - (BOOL)columnIndexIsNull:(int)columnIdx; - (BOOL)columnIsNull:(NSString*)columnName; - (NSString * _Nullable)stringForColumn:(NSString*)columnName; - (NSData * _Nullable)dataForColumn:(NSString*)columnName; - (NSData * _Nullable)dataNoCopyForColumn:(NSString *)columnName NS_RETURNS_NOT_RETAINED; - (NSDate * _Nullable)dateForColumn:(NSString*)columnName; - (id _Nullable)objectForColumn:(NSString*)columnName; - (id _Nullable)objectForColumnName:(NSString * _Nonnull)columnName __deprecated_msg("Use objectForColumn instead"); - (id _Nullable)objectForKeyedSubscript:(NSString *)columnName; - (long)longForColumn:(NSString*)columnName; - (long long int)longLongIntForColumn:(NSString*)columnName; - (BOOL)boolForColumn:(NSString*)columnName; - (double)doubleForColumn:(NSString*)columnName; // On top of the previous compatibility warning, GRDBObjc will crash with // a fatal error when indexes are out of range. FMDB would always return a // value. - (NSData * _Nullable)dataForColumnIndex:(int)columnIdx; - (NSData * _Nullable)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED; - (NSDate * _Nullable)dateForColumnIndex:(int)columnIdx; - (id _Nullable)objectForColumnIndex:(int)columnIdx; - (id _Nullable)objectAtIndexedSubscript:(int)columnIdx; - (long)longForColumnIndex:(int)columnIdx; - (long long int)longLongIntForColumnIndex:(int)columnIdx; - (BOOL)boolForColumnIndex:(int)columnIdx; - (double)doubleForColumnIndex:(int)columnIdx; - (NSString * _Nullable)stringForColumnIndex:(int)columnIdx; // On top of the previous compatibility warnings, GRDBObjc also crashes // with a fatal error when database contains a value that is not // representable in the requested type. FMDB would always return a value. - (int)intForColumnIndex:(int)columnIdx; - (int)intForColumn:(NSString*)columnName; - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx; - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName;
祝大家Happy GRDB!