【问题标题】:How use sqlite + fdbm library with threading on the iPhone如何在 iPhone 上使用带有线程的 sqlite + fdbm 库
【发布时间】:2010-11-08 05:49:28
【问题描述】:

this SO question相关,我想把数据加载放在后台。

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

this SO thread 中说方法是使用 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];
}

db 接口的代码位于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);
}

【问题讨论】:

    标签: objective-c iphone multithreading sqlite nsoperation


    【解决方案1】:

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

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

    这就是NSOperation 的用武之地。对它进行子类化可以让您将一堆代码视为“单个封装任务”——您可能希望针对非并发操作进行设计。类文档中有详细说明如何在 NSOperation 中实现逻辑。


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

    由于问题出现在checkError 中,并且该方法是从链接的 .m 文件中的多个位置调用的,因此您很有可能在不正确的时间调用hadError。 (是在一个事务关闭之后调用吗?如果在下一个事务开始之后调用呢?)

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

    如果您还没有,请阅读this Apple article on Objective-C thread synchronization。接下来,查阅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:。力求准确性、可读性和匹配约定。

    【讨论】:

    • cocoabuilder.com/archive/message/cocoa/2009/4/29/235715 中看起来像一个人使用 NSOperation 并且仍然得到错误。我做的事情和他类似。
    • 当您提供这么少的细节时,请不要指望一个完整的解决方案。我试图诊断这个问题基本上是盲目的,我看不出你希望我如何猜测一个确切的解决方案。您还没有说您是否正在使用多个线程,也没有解释您的 currentDbSingleton 变量的“Db”类型。我试图提供帮助,但只能用可用的信息做这么多。如果您想要一个有用的回答,请完善您的问题。
    • 感谢您提供详细信息。我想知道是否最好将所有方法都放在@syncronize 中? (与此同时,我正在执行您建议的重构......)
    • 关于@synchronized 要了解的一点是,它会根据需要为您创建一个 NSLock,但它更适合更细粒度的控制。如果要跨多个方法同步,最好自己使用 NSLock。
    【解决方案2】:

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

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

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

    猜你喜欢
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    相关资源
    最近更新 更多