GRDBObjc 0.8

GRDBObjc 0.8

Gwendal RouéGwendal RouéGwendal Roué 维护。



  • Gwendal Roué

GRDBObjc Swift 4.2 Swift 5License

为 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之上编译。当然,魔鬼在细节中,以下是一些详细的兼容性图表

FMDatabaseQueueFMResultSet等类现在由GRDB支持。从Objective-C初始化的数据库队列可在Swift中使用,反之亦然。请参阅以下安装程序中的详细说明。

安装

强制警告:GRDBObjc软件仍然非常年轻。它经过测试,但尚未为很多实际应用提供燃料。

熟悉Swift文档中的“同一项目中使用Swift和Objective-C”章节。

查看FMDB兼容性图表

如果需要的一些API缺失,或者你发现了一个错误或令人惊讶的行为,请准备好提出拉取请求

可以使用Cocoapods安装GRDObjc

  1. 请确保您的应用程序目标使用自动引用计数编译Objective-C:CLANG_ENABLE_OBJC_ARC = YES

  2. 在应用程序目标中指定构建设置:SWIFT_VERSION = 4.0

  3. 在Podfile中将FMDB替换为GRDBObjc

    -pod 'FMDB'
    +pod 'GRDBObjc'
  4. 运行pod install

  5. 替换Objective-C导入指令

    -#import <fmdb/FMDB.h>
    +#import <GRDBObjc/GRDBObjc.h>
  6. 运行并测试您的应用程序:确保您的Objective-C代码能很好地处理GRDBObjc。

  7. 通过桥接头将Objective-C的FMDatabaseQueue公开给Swift

    不要在Swift中使用FMDB API:这不是这个库的目标!相反,从FMDatabaseQueue中提取真正的GRDB DatabaseQueue,并确保Swift代码仅使用GRDB

    import 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;

:bowtie: 祝大家Happy GRDB!