i'm using the amazing FMDB project in my app in development, i have a NSOperation like this:
- (void)main
{
@autoreleasepool {
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:@"pathDB"]];
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *toQuery;
if (self._id == nil) {
toQuery = [db executeQuery:@"SELECT id,language,update_time FROM task"];
while ([toQuery next]) {
[myarray addObject:[toQuery resultDictionary]];
}
}];
for (int i = 0; i<[myarray count]; i++){
...Do Something
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *checkImgQuery = [db executeQuery:@"SELECT img_url,img_path FROM task WHERE id = ? AND language = ?",myTask.id ,myTask.lang];
while ([checkImgQuery next]) {
if (![[checkImgQuery stringForColumn:@"img_url"] isEqualToString:myTask.img]) {
NSData *my_img = [NSData dataWithContentsOfURL:[NSURL URLWithString:myTask.img]];
if (my_img != nil) {
NSError *er;
[my_img writeToFile:[checkImgQuery stringForColumn:@"img_path"] options:NSDataWritingAtomic error:&er];
//In the line under here the code block, the app still running, but this operation doesn't
//go over this task
[db executeUpdate:@"UPDATE task SET img_url = ? WHERE id = ? AND language = ?",myTask.img,[NSNumber numberWithInt:myTask.id],[NSNumber numberWithInt:myTask.language];
NSLog(@"%@",[db lastErrorMessage]);
}
...Do Something
}
}
}
}];
}
}
The problem is in [db executeUpdate:...]
that sometime works with no problem and sometime freeze and doesn't go over that line, the NSLog
i have put there doesn't print anything, the app doesn't crash and continue working, but the thread is stuck there, if i shutdown the run of the app, and i restart it again the thread doesn't stop on the same task, but random on another, with no criteria, some time one works, and some time doesn't...anyone can help?
There are a couple issues that leap out at me, one or more of which may be contributing to your problem:
I notice that you're creating a
FMDatabaseQueue
object locally. You should only have oneFMDatabaseQueue
object shared for the entire app (I put it in a singleton). The purpose of the database queue is to coordinate database interactions, and it can't reasonably do that if you're creating newFMDatabaseQueue
objects all over the place.I'd advise against having an
inDatabase
block in which you're synchronously downloading a bunch of images from the network.When you submit an
inDatabase
task, anyinDatabase
calls on other threads using the sameFMDatabaseQueue
(and they should use the same queue, or else you're defeating the purpose in having a queue in the first place) will not proceed until the one running in your operation does (or vice versa).When doing database interaction from multiple threads, coordinated by the
FMDatabaseQueue
serial queue, you really want to make sure that you "get in and get out" as quickly as possible. Don't embed potentially slow network calls in the middle of theinDatabase
block, or else all other database interaction will be blocked until it finishes.So, do an
inDatabase
to identify the images that need to be downloaded, but that's it. Then outside of theinDatabase
call, retrieve your images, and if you need to update image paths or the like, separateinDatabase
call do to do that. But don't include anything slow and synchronous inside theinDatabase
block.I also notice that you're doing a
SELECT
ontask
table, keeping thatFMRecordSet
open, and then trying to update the same record. You want to open your record set, retrieve what you need, and close that recordset before you try to update the same record you retrieved in your recordset.Always close the
FMResultSet
before you try to do theexecuteUpdate
that updates the same record.A bit unrelated, but I might suggest you consider including
img_url
andimg_path
in your originalSELECT
statement, that way your array of dictionary entries will already have everything you need and it saves you from have to do that secondSELECT
at all.If you're wondering what the
FMDatabaseQueue
singleton might look like, you might have aDatabaseManager
singleton whose interface looks like:and the implementation might look like:
Then, any code that needs to interact with the database can retrieve the queue like so: