Как использовать библиотеку sqlite + fdbm с многопоточностью на iPhone

В связи с этим SO-вопросом я хочу перевести загрузку данных в фоновый режим.

Тем не менее, я получаю ошибки «библиотечная процедура, вызванная вне последовательности».

В этот поток SO скажите, что способ использовать NSOperation, но, глядя на образцы в Интернете, я не знаю, как это может решить проблему.

Я разделяю одно соединение sqlite с шаблоном singleton:

@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.

Ошибка произошла при попытке вызвать функции sqlite из FDBM.

Например произошло здесь:

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

person mamcx    schedule 05.07.2009    source источник


Ответы (2)


Метод singleton - хорошая идея, хотя не похоже, что вы на самом деле инициализируете currentDbSingleton где-либо... Предполагая, что вы исправите это и вернете действительное соединение с БД, я не думаю, что это ваша проблема.

Упомянутые вами ошибки "вызов библиотечной процедуры вне последовательности" подсказывают мне, что используемая вами библиотека (SQLite или FMDB) ожидает, что вызовы методов/функций будут выполняться в определенной последовательности. То, что у вас, вероятно, есть проблема параллелизма, когда два или более потока делают вызовы к одной и той же библиотеке, и хотя каждый из них может использовать правильный порядок, если они, так сказать, «разговаривают в одно и то же время», библиотека может получить вызовы вне ожидаемой последовательности. Вы хотите, чтобы набор вызовов рассматривался как атомарная единица, чтобы они не могли перекрываться или смешиваться.

Здесь на помощь приходит NSOperation. код как «единую инкапсулированную задачу» — вы, вероятно, захотите спроектировать непараллельную операцию. В документации по классу есть подробности, которые объясняют, как реализовать вашу логику в NSOperation.


Изменить: (после того, как автор вопроса уточнил вопрос, добавив контекст)

Поскольку проблема возникает в checkError, и этот метод вызывается из нескольких мест в вашем связанном .m-файле, велика вероятность того, что вы вызываете hadError в неправильное время. (Вызывается ли он после закрытия транзакции? Что, если он вызывается после начала следующей транзакции?)

Например, что произойдет, если fillCache будет вызвана, когда предыдущий вызов все еще обращается к базе данных? Ваши методы управления транзакциями в этом отношении выглядят крайне подозрительно. Например, если кто-то вызывает beginTransaction, а inTransaction равно YES, метод возвращает ничего не делая (то есть он вообще не вызывает ivar FMDatabase). Вероятно, вы хотите, чтобы второй вызывающий объект ждал, пока первый вызывающий объект не завершит свою транзакцию. (Если FMDatabase не поддерживает одновременные транзакции, в этом случае вы все равно захотите вызывать beginTransaction для него.)

Если вы еще этого не сделали, прочтите эту статью Apple о цели- Синхронизация потоков C. Затем обратитесь к документации по NSLock< /a> (в частности, lockBeforeDate:) и пример кода. Внутри вашего метода -[Db beginTransaction] вы, вероятно, захотите заблокировать получение блокировки.

У вас также есть несколько специфических методов класса, таких как +allocWithZone: — выберите +inizialize (который автоматически вызывается средой выполнения при первом обращении к классу), если вы можете, чтобы класс мог позаботиться о своей инициализации без необходимости для ручного вызова. (Я предполагаю, что вы вызываете +alloc, затем -initWithName:, а затем передаете его обратно в +setCurrentDb. Удобный метод, такой как +initializeWithPath:, который обрабатывает все это, был бы намного чище.)

Есть множество других ошибок, таких как тот факт, что +setCurrentDb: может подкачать объект-синглтон независимо от того, находится ли транзакция в процессе (и старый синглтон не выпущен), +currentDb вызывает исключение, где он, вероятно, должен просто создать объект-синглтон. экземпляр singleton и т. д. Однако самые большие проблемы, с которыми вы сталкиваетесь, — это правильный параллелизм. Я думаю, что реализация блокировок для защиты ссылки FMDatabase — это шаг в правильном направлении, но просто обертывание метода X в NSOperation не сделает этого за вас. Каждая точка в вашем коде, которая ссылается на theDb без гарантии того, что никто другой не делает этого, может привести к сбою. Не расстраивайтесь, если это кажется трудным, потому что это так.

Последнее случайное предложение: измените методы TypeForField:Type: и ValueForField:Name:Type: на typeForFieldName:typeName: и valueForResultSet:fieldName:typeName: соответственно. Стремитесь к точности, удобочитаемости и соответствию правил.

person Quinn Taylor    schedule 06.07.2009
comment
В cocoabuilder.com/archive/message/cocoa/2009/4 /29/235715 похоже, что парень использует NSOperation и все равно получает ошибку. Я делаю что-то похожее на него. - person mamcx; 06.07.2009
comment
Пожалуйста, не ожидайте полностью сформированного решения, когда вы предоставляете так мало деталей. Я пытаюсь диагностировать проблему практически вслепую, и я не понимаю, как вы ожидаете, что я угадаю точное решение. Вы не сказали, используете ли вы несколько потоков, или объяснили тип Db вашей переменной currentDbSingleton. Я пытаюсь быть полезным, но мало что могу сделать с имеющейся информацией. Если вы хотите получить полезный ответ, пожалуйста, уточните свой вопрос. - person Quinn Taylor; 06.07.2009
comment
Спасибо за подробности. Интересно, не лучше ли поместить все методы в @syncronize? (Тем временем я делаю рефакторинг, который вы предлагаете...) - person mamcx; 07.07.2009
comment
Одна вещь, которую нужно знать о @synchronized, заключается в том, что он создает NSLock по запросу для вас, но это лучше для более тонкого контроля. Если вы хотите синхронизировать несколько методов, предпочтительнее использовать NSLock самостоятельно. - person Quinn Taylor; 07.07.2009

Наконец-то я нашел рабочее решение.

Идея состоит в том, чтобы создать пул соединений с базой данных и дважды открыть db. Используйте одно соединение для основного потока, а другое — для фона.

Теперь все находится в http://code.google.com/p/chibiorm/.

person mamcx    schedule 24.07.2010
comment
Проверьте code.google.com/p/chibiorm/source/browse. /src/DbBackground.m и изверги... - person mamcx; 10.07.2012