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

时间:2009-07-05 17:44:16

标签: objective-c iphone multithreading sqlite nsoperation

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);
}

2 个答案:

答案 0 :(得分:3)

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

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

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


编辑:(在提问者用附加背景澄清了问题后)

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

例如,如果在前一次调用仍在访问数据库时调用fillCache会发生什么?在这方面,您的交易管理方法看起来非常可疑。例如,如果某人调用beginTransactioninTransactionYES,则该方法将返回而不执行任何操作(即,它根本不会调用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引用是朝着正确方向迈出的一步,但只是在NSOperation中包装方法X不会为你做。代码中引用theDb的每个点都不会保证没有其他人这样做会导致崩溃。如果这看起来很难,请不要感到难过,因为它是。

上次随机建议:将您的方法TypeForField:Type:ValueForField:Name:Type:分别更改为typeForFieldName:typeName:valueForResultSet:fieldName:typeName:。力求准确性,可读性和匹配惯例。

答案 1 :(得分:2)