3

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

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

这个 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];
}

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

2 回答 2

3

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

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

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


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

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

例如,如果fillCache在前一个调用仍在访问数据库时调用 会发生什么?在这方面,您的事务管理方法看起来非常可疑。例如,如果有人调用beginTransactionand inTransactionis YES,则该方法不做任何事情就返回(也就是说,它根本不调用 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:

于 2009-07-06T15:51:41.240 回答
2

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

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

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

于 2010-07-24T17:09:49.650 回答