没有僵尸或未初始化变量的神秘EXC_BAD_ACCESS

Pio*_*otr 7 objective-c

我的应用程序崩溃在以下行:

sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
Run Code Online (Sandbox Code Playgroud)

在FMDB sqlite包装器的方法中:

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {

if (![self databaseExists]) {
    return 0x00;
}

if (inUse) {
    [self warnInUse];
    return 0x00;
}

[self setInUse:YES];

FMResultSet *rs = nil;

int rc                  = 0x00;
sqlite3_stmt *pStmt     = 0x00;
FMStatement *statement  = 0x00;

if (traceExecution && sql) {
    NSLog(@"%@ executeQuery: %@", self, sql);
}

if (shouldCacheStatements) {
    statement = [self cachedStatementForQuery:sql];
    pStmt = statement ? [statement statement] : 0x00;
}

int numberOfRetries = 0;
BOOL retry          = NO;

if (!pStmt) {
    do {
        retry   = NO;
        const char *sqlStatement = [sql UTF8String];
        rc      = sqlite3_prepare_v2(db, sqlStatement, -1, &pStmt, 0);

        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
            retry = YES;
            usleep(20);

            if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
                NSLog(@"Database busy");
                sqlite3_finalize(pStmt);
                [self setInUse:NO];
                return nil;
            }
        }
        else if (SQLITE_OK != rc) {


            if (logsErrors) {
                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                NSLog(@"DB Query: %@", sql);
#ifndef NS_BLOCK_ASSERTIONS
                if (crashOnErrors) {
                    NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                }
#endif
            }

            sqlite3_finalize(pStmt);

            [self setInUse:NO];
            return nil;
        }
    }
    while (retry);
}

id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)

while (idx < queryCount) {

    if (arrayArgs) {
        obj = [arrayArgs objectAtIndex:idx];
    }
    else {
        obj = va_arg(args, id);
    }

    if (traceExecution) {
        NSLog(@"obj: %@", obj);
    }

    idx++;

    [self bindObject:obj toColumn:idx inStatement:pStmt];
}

if (idx != queryCount) {
    NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
    sqlite3_finalize(pStmt);
    [self setInUse:NO];
    return nil;
}

[statement retain]; // to balance the release below

if (!statement) {
    statement = [[FMStatement alloc] init];
    [statement setStatement:pStmt];

    if (shouldCacheStatements) {
        [self setCachedStatement:statement forQuery:sql];
    }
}

// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[openResultSets addObject:openResultSet];

statement.useCount = statement.useCount + 1;

[statement release];    

[self setInUse:NO];

return rs;
Run Code Online (Sandbox Code Playgroud)

}

该应用程序崩溃与EXC_BAD_ACCESS.我试图通过调试NSZombieEnabled和malloc_history找出原因,但它没有给我任何答案.另外 - 调试器告诉我sql变量的保留计数非常大(这可能是因为它是一个静态NSString) - 所以EXC_BAD_ACCESS不应该是因为sql对象被过度释放.

有没有人有任何想法如何进一步调试,以找出问题所在?

Pio*_*otr 3

解决方案:问题是我的数据库被多个线程访问。即使所有线程都同步访问数据库句柄,对于 3.3.1 之前的 sqlite 版本(iOS 使用 3.0),您也不能安全地跨线程使用相同的数据库句柄。

我的解决方案是为尝试访问数据库的每个线程创建数据库的按需句柄,如下所示:

- (ADatabaseConnection *)databaseConnection {

NSDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSString *key = @"aDatabaseConnection";
ADatabaseConnection *connection = [dictionary objectForKey:key];
if (connection == nil) {

    connection = [[[ADatabaseConnection alloc] initWithDatabase:self] autorelease];
    [dictionary setValue:connection forKey:key];
}
return connection;
}
Run Code Online (Sandbox Code Playgroud)

请注意,对于 sqlite 版本 >= 3.3.1,不需要这样做,因为可以跨线程使用相同的句柄。

另一件需要记住的重要事情是,即使您使用这种方法跨线程安全地使用同一个数据库,同步对数据库的访问也可能是明智的,这样您无论如何都不会同时访问它,以避免数据库锁定错误。我两者都做,每个线程使用一个句柄并在数据库上同步。

  • 这种情况在 iOS 8.1 和 sqlite 3.7 中再次发生。 (2认同)