如何使用sqlite + iPhone 上带有线程的 fdbm 库

发布于 2024-07-26 02:26:43 字数 2760 浏览 4 评论 0原文

这个SO问题相关,我想将数据加载放在后台。

但是,我收到“库例程调用不按顺序”错误。

这个SO线程中说方法是使用 NSOperation,但是查看网络上的示例我不知道如何解决该问题。

我与单例模式共享一个 sqlite 连接:

@interface Db : NSObject {
    NSString *path;
    FMDatabase* theDb;
    BOOL isOpen;
}

@property (retain, nonatomic) FMDatabase *theDb;
@property (retain, nonatomic) NSString *path;
@property (nonatomic) BOOL isOpen;
--------
static Db *currentDbSingleton = nil;
#pragma mark Global access

+(id)currentDb {
    @synchronized(self) {
        if (!currentDbSingleton) {
            NSString *reason = NSLocalizedString(@"The database is not set globally",
                                                 @"Error Db: database is not set");
            NSException *e = [NSException exceptionWithName:@"DBError"                        
                                                     reason:reason;
                                                   userInfo:nil];
            @throw e;
        }
    }
    return currentDbSingleton;  
}

所以打开两次相同的数据库更难......

有什么想法吗?

编辑:

我确认错误是在调用 sqlite. 我使用 FDBM 作为调用它的薄包装器。

我正在运行 2 个线程:主线程和用于加载数据的后台任务。 我这样运行它:

- (void) fillCache:(NSString *)theTable {
    [NSThread detachNewThreadSelector:@selector(fillCacheBackground:)
                             toTarget:self
                           withObject:theTable];
}

- (void)loadComplete {
    [self.table reloadData];
}

- (void) fillCacheBackground:(NSString *)theTable {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Db *db= [Db currentDb];
    [db beginTransaction];
        ..... STUFF HERE
    [db commitTransaction];
    //Tell our callback what we've done
    [self performSelectorOnMainThread:@selector(loadComplete) 
                           withObject:nil 
                        waitUntilDone:YES];
    [pool drain];
}

数据库接口的代码位于 http://code.google.com/p/chibiorm/source/browse/#svn/trunk/src — 特别是 Db.h/m,它是唯一与 fdbm/sqlite 交互的单元。

尝试从 FDBM 调用 sqlite 函数时发生错误。

例如发生在这里:

-(void) checkError {
    if ([self.theDb hadError]) { // <====ERROR HERE
        NSLog(@"Err %d: %@", [self.theDb lastErrorCode], [self.theDb]);
    }
}

这调用了 FDBM 代码:

- (BOOL) hadError {
    int lastErrCode = sqlite3_errcode(db);
    return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW);
}

Related to this SO question, I want to put the data loading in the background.

However, I get 'library routine called out of sequence' errors.

In this SO thread say that the way is using NSOperation, but looking on the samples on the web I not know how that could solve the issue.

I share a single sqlite connection with the singleton pattern:

@interface Db : NSObject {
    NSString *path;
    FMDatabase* theDb;
    BOOL isOpen;
}

@property (retain, nonatomic) FMDatabase *theDb;
@property (retain, nonatomic) NSString *path;
@property (nonatomic) BOOL isOpen;
--------
static Db *currentDbSingleton = nil;
#pragma mark Global access

+(id)currentDb {
    @synchronized(self) {
        if (!currentDbSingleton) {
            NSString *reason = NSLocalizedString(@"The database is not set globally",
                                                 @"Error Db: database is not set");
            NSException *e = [NSException exceptionWithName:@"DBError"                        
                                                     reason:reason;
                                                   userInfo:nil];
            @throw e;
        }
    }
    return currentDbSingleton;  
}

So is harder open twice the same db....

Any ideas?

EDIT:

I confirmed the error is in calling sqlite. I use FDBM as thin wrapper for calling it.

I'm running 2 threads: the main and a background task for loading of the data. I run it this way:

- (void) fillCache:(NSString *)theTable {
    [NSThread detachNewThreadSelector:@selector(fillCacheBackground:)
                             toTarget:self
                           withObject:theTable];
}

- (void)loadComplete {
    [self.table reloadData];
}

- (void) fillCacheBackground:(NSString *)theTable {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Db *db= [Db currentDb];
    [db beginTransaction];
        ..... STUFF HERE
    [db commitTransaction];
    //Tell our callback what we've done
    [self performSelectorOnMainThread:@selector(loadComplete) 
                           withObject:nil 
                        waitUntilDone:YES];
    [pool drain];
}

The code for the db interface is at http://code.google.com/p/chibiorm/source/browse/#svn/trunk/src — specifically the Db.h/m that are the only units that interface with fdbm/sqlite.

The error happend when try to call sqlite functions from FDBM.

For example happend here:

-(void) checkError {
    if ([self.theDb hadError]) { // <====ERROR HERE
        NSLog(@"Err %d: %@", [self.theDb lastErrorCode], [self.theDb]);
    }
}

This call the FDBM code:

- (BOOL) hadError {
    int lastErrCode = sqlite3_errcode(db);
    return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小瓶盖 2024-08-02 02:26:43

单例方法是一个好主意,尽管它看起来并不像您实际上在任何地方初始化 currentDbSingleton ...假设您修复了该问题并返回了有效的数据库连接,我认为这不是你有问题。

您提到的“乱序调用库例程”错误提示我,您正在使用的库(SQLite 或 FMDB)期望以特定顺序进行方法/函数调用。 您可能遇到的并发问题是两个或多个线程正在调用同一个库,虽然每个线程都可能使用正确的顺序,但如果它们“同时通话”,可以这么说,库可能会收到调用顺序不符合预期。 您想要的是将一组调用视为一个原子单元,以便它们不能重叠或混合。

这就是 NSOperation 发挥作用的地方。子类化它可以让你处理一堆将代码编写为“单个封装任务”——您可能希望针对非并发操作进行设计。 类文档包含解释如何在 NSOperation 中实现逻辑的详细信息。


编辑:(在询问者通过附加上下文澄清问题后)

由于问题发生在 checkError 中,并且从链接的 .m 文件中的多个位置调用该方法,因此您很有可能在错误的时间调用 hadError。 (它是在事务关闭后调用吗?如果在下一个事务开始后调用它会怎样?)

例如,如果在上一个调用仍在访问数据库时调用 fillCache 会发生什么? 在这方面,您的事务管理方法看起来非常可疑。 例如,如果有人调用 beginTransaction 并且 inTransactionYES,则该方法将返回而不执行任何操作(即,它不会调用FMDatabase ivar 根本)。 您可能希望第二个调用者等待第一个调用者完成其事务。 (除非 FMDatabase 支持并发事务,在这种情况下,您无论如何都需要对其调用 beginTransaction。)

如果您还没有这样做,请阅读 这篇关于 Objective-C 线程同步的 Apple 文章。 接下来,请查阅 NSLock(特别是lockBeforeDate:) 和相关示例代码。 在您的 -[Db beginTransaction] 方法中,您可能希望阻止获取锁。

您还有几个特殊的类方法,例如 +allocWithZone: — 如果可以的话,选择使用 +inizialize(首次引用该类时,运行时会自动调用该方法),这样该类就可以自行初始化,而无需进行初始化。用于手动呼叫。 (我猜你先调用 +alloc,然后调用 -initWithName:,然后将其反馈给 +setCurrentDb。诸如 +initializeWithPath: 之类的便捷方法可以更干净地处理所有这些问题。)

还有许多其他问题,例如事实上, +setCurrentDb: 可以交换单例对象,无论事务是否正在进行(并且旧的单例未释放), +currentDb 会引发异常,它可能应该只创建单例实例等。但是,最大的您面临的问题是如何正确实现并发。 我认为实现锁来保护 FMDatabase 引用是朝着正确方向迈出的一步,但仅仅将方法 X 包装在 NSOperation 中并不能满足您的需要。 代码中引用 theDb 的每一点都不能保证没有其他人这样做,都有崩溃的风险。 如果这看起来很难,请不要感到难过,因为事实确实如此。

最后随机建议:将方法 TypeForField:Type:ValueForField:Name:Type: 更改为 typeForFieldName:typeName:valueForResultSet:分别为 fieldName:typeName:。 力求准确性、可读性和匹配约定。

The singleton method is a fine idea, although it doesn't look like you're actually initializing currentDbSingleton anywhere... Assuming you fix that and you return a valid DB connection, I don't think that's you're problem.

The 'library routine called out of sequence' errors you mention tip me off that the library you're using (SQLite or FMDB) expects method/function calls to be made in a particular sequence. What you likely have is a concurrency problem where two or more threads are making calls to the same library, and while each may use the correct order, if they're "talking at the same time", so to speak, the library may receive calls out of the expected sequence. What you want is for a set of calls to be treated as an atomic unit, such that they cannot overlap or intermingle.

This is where NSOperation comes in. Subclassing it allows you treat a bunch of code as a "single encapsulated task" — you'll probably want to design for non-concurrent operation. The class documentation has details that explain how to implement your logic within an NSOperation.


Edit: (After asker clarified the question with addition context)

Since the problem is occurring in checkError, and that method is called from multiple places in your linked .m file, there's a good chance that you're calling hadError at an incorrect time. (Is it called after a transaction is closed? What if it is called after the next transaction has begun?)

For example, what would happen if fillCache is called while the previous call is still accessing the database? Your transaction management methods look extremely suspect in this regard. For example, if someone calls beginTransaction and inTransaction is YES, the method returns without doing anything (that is, it doesn't call to the FMDatabase ivar at all). What you probably want is for the second caller to wait until the first caller finishes its transaction. (Unless FMDatabase supports concurrent transactions, in which case you'd want to call beginTransaction on it regardless.)

If you haven't already, read this Apple article on Objective-C thread synchronization. Next, consult the documentation for NSLock (particularly lockBeforeDate:) and associated sample code. Inside your -[Db beginTransaction] method, you'll probably want to block on obtaining a lock.

You also have several peculiar class methods, such as +allocWithZone: — opt to use +inizialize (which is called automatically by the runtime when the class is first referenced) if you can, so the class can take care of initializing itself without the need for a manual call. (I'm guessing you call +alloc, then -initWithName:, then feed it back to +setCurrentDb. A convenience method such as +initializeWithPath: that handles all that would be much cleaner.)

There are numerous other gotchas, such as the fact that +setCurrentDb: could swap out the singleton object regardless of whether a transaction is in process (and the old singleton is not released), +currentDb raises an exception where it should probably just create the singleton instance, etc. However, the biggest problems you're facing are getting concurrency right. I think that implementing locks to protect the FMDatabase reference is a step in the right direction, but just wrapping method X in an NSOperation isn't going to do it for you. Every point in your code that references theDb without the guarantee that nobody else is doing so risks a crash. Don't feel bad if this seems hard, because it is.

Last random suggestion: change your methods TypeForField:Type: and ValueForField:Name:Type: to typeForFieldName:typeName: and valueForResultSet:fieldName:typeName: respectively. Strive for accuracy, readability, and matching convention.

ぶ宁プ宁ぶ 2024-08-02 02:26:43

我终于找到了一个可行的解决方案。

这个想法是建立一个数据库连接池并打开两次数据库。 将一个连接用于主线程,另一个连接用于后台。

现在一切都在 http://code.google.com/p/chibiorm/

I finally found a working solution.

The idea is build a database pool of connection and open twice the db. Use one connection for the main thread and the another for the background.

Everything is now in http://code.google.com/p/chibiorm/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文