在我的iOS应用程序中,我读取了一个大型CSV文件并将其加载(通过插入)在SQLite数据库中。
这是我的代码:
- (void) parseCSVFile:(NSString *)path
{
__block BOOL isFileOk = NO;
__block BOOL atLeastOneRowProcessed = NO;
NSFileManager *fileMgr = [[NSFileManager alloc] init];
unsigned long totalSize = [[[fileMgr attributesOfItemAtPath:path error:nil] objectForKey:@"NSFileSize"] intValue];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"mydatabase.sqlite"];
const char *dbpath = [writableDBPath UTF8String];
dispatch_queue_t databaseQueue = [(AppDelegate *)[[UIApplication sharedApplication] delegate] databaseQueue];
dispatch_sync(databaseQueue, ^{
sqlite3 *ddbb;
NSLog(@"INIT DATABASE");
if (sqlite3_open(dbpath, &ddbb) == SQLITE_OK)
{
char* errorMessage;
sqlite3_exec(ddbb, "DROP TABLE IF EXISTS datainfile", NULL, NULL, &errorMessage);
sqlite3_exec(ddbb, "CREATE TABLE \"datainfile\" (\"field1\" TEXT PRIMARY KEY NOT NULL ,\"field2\" TEXT DEFAULT (null) ,\"field3\" TEXT DEFAULT (null) ,\"field4\" TEXT)", NULL, NULL, &errorMessage);
sqlite3_exec(ddbb, "BEGIN TRANSACTION", NULL, NULL, &errorMessage);
char buffer[] = "INSERT OR IGNORE INTO datainfile VALUES (?1, ?2, ?3, ?4)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(ddbb, buffer, (int)strlen(buffer), &stmt, NULL);
char *pathUTF8 = (char*)[path UTF8String];
FILE* stream = fopen(pathUTF8, "r");
char *field1 = "";
char *field2 = "";
char *field3 = "";
char *field4 = "";
NSLog(@"READING FILE");
unsigned long processedSize = 0;
char line[1024];
while(fgets(line, 1024, stream))
{
processedSize = processedSize + strlen(line) * sizeof(char);
char* tmp = strdup(line);
field1 = strtok (tmp,", \t");
field2 = strtok (NULL, ", \t");
field3 = strtok (NULL, ", \t");
field4 = strtok (NULL, "\r\n");
atLeastOneRowProcessed = YES;
if(!isFileOk)
{
isFileOk = [self checkIfFileIsACorrectCSVWithField1:field1 field2:field2 field3:field3 andField4:field4];
if(!isFileOk)
{
if([_delegate respondsToSelector:@selector(fileParserError:)])
[_delegate fileParserError:_reportErrorMessage];
free(tmp);
break;
}
}
sqlite3_bind_text(stmt, 1, field1, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, field2, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, field3, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, field4, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
NSLog(@"ERROR: Commit Failed in line %s!\n", line);
// If there was an error we try to repeat the commit
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, field1, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, field2, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, field3, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, field4, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
NSLog(@"ERROR: Commit failed again");
}
else
{
NSLog(@"ERROR: Commit success");
}
}
sqlite3_reset(stmt);
free(tmp);
}
if(isFileOk)
{
NSLog(@"File read and now the commit starts");
sqlite3_exec(ddbb, "COMMIT TRANSACTION", NULL, NULL, &errorMessage);
_phase = PARSER_PHASE_CREATING_INDEX;
sqlite3_exec(ddbb, "CREATE INDEX field1 ON datainfile(field1);", NULL, NULL, &errorMessage);
sqlite3_finalize(stmt);
NSLog(@"Commit has ended");
}
}
sqlite3_close(ddbb);
});
if(isFileOk)
{
if([_delegate respondsToSelector:@selector(fileParserFinished)])
[_delegate fileParserFinished];
}
else
{
if(!atLeastOneRowProcessed)
{
_reportErrorMessage = NSLocalizedString(@"TXT_FILEERROR_NOPROCESSED", Nil);
if([_delegate respondsToSelector:@selector(fileParserError:)])
[_delegate fileParserError:_reportErrorMessage];
}
}
}
databaseQueue是以这种方式创建的AppDelegate的一个属性:
_databaseQueue = dispatch_queue_create("com.mycompany.myapp.database", 0);
嗯,这几乎总是很好用。但有时它会发生奇怪的崩溃。这是日志:
Crashed: com.mycompany.myapp.database
0 libsystem_kernel.dylib 0x1865d1014 __pthread_kill + 8
1 libsystem_pthread.dylib 0x18669b264 pthread_kill + 112
2 libsystem_c.dylib 0x1865459c4 abort + 140
3 libsystem_c.dylib 0x186545ad8 _UTF2_init + 82
4 libsystem_c.dylib 0x186559c7c __chk_fail_overlap + 54
5 libsystem_c.dylib 0x186559c44 __chk_fail + 18
6 libsystem_c.dylib 0x18655a04c __vsprintf_chk + 134
7 MyApp 0x1000c151c __32-[FileParser parseCSVFile:]_block_invoke (FileParser.m:650)
8 libdispatch.dylib 0x18648e9a0 _dispatch_client_callout + 16
9 libdispatch.dylib 0x18649bee0 _dispatch_barrier_sync_f_invoke + 84
10 MyApp 0x1000c1084 -[FileParser parseCSVFile:] (FileParser.m:734)
11 MyApp 0x1000bed28 -[FileParser readFileWithPath:andDelegate:] (FileParser.m:147)
12 MyApp 0x100041a64 __57-[HomeViewController readFileWithPath:]_block_invoke_2 (HomeViewController.m:625)
13 libdispatch.dylib 0x18648e9e0 _dispatch_call_block_and_release + 24
14 libdispatch.dylib 0x18648e9a0 _dispatch_client_callout + 16
15 libdispatch.dylib 0x18649d0d4 _dispatch_queue_override_invoke + 644
16 libdispatch.dylib 0x18649ea50 _dispatch_root_queue_drain + 540
17 libdispatch.dylib 0x18649e7d0 _dispatch_worker_thread3 + 124
18 libsystem_pthread.dylib 0x186697100 _pthread_wqthread + 1096
19 libsystem_pthread.dylib 0x186696cac start_wqthread + 4
它发生在我的FileParser的第734行和第650行,这些行是:
(第734行)第二个 if(isFileOk)
离开dispatch_sync后的第一行。
(第650行) processedSize = processedSize + strlen(line)* sizeof(char);
阅读CSV文件的第一行。
在我看来,dispatch_sync在执行之前会保留代码,同时调用块外的一行和块内的一行。
我必须补充一点,当发生此崩溃时,在块外部崩溃的行总是 if(isFileOk),但是在块内崩溃的行会有所不同。
它会发生什么?