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 a DatabaseManager
singleton whose interface looks like:
// DatabaseManager.h
@import Foundation;
@import SQLite3;
#import "FMDB.h"
NS_ASSUME_NONNULL_BEGIN
@interface DatabaseManager : NSObject
@property (nonatomic, strong, readonly) FMDatabaseQueue *queue;
@property (class, readonly, strong) DatabaseManager *sharedManager;
- (id)init __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
+ (id)new __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
@end
NS_ASSUME_NONNULL_END
and the implementation might look like:
// DatabaseManager.m
#import "DatabaseManager.h"
@implementation DatabaseManager
+ (DatabaseManager *)sharedManager {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if ((self = [super init])) {
_queue = [[FMDatabaseQueue alloc] initWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:@"pathDB"]];
}
return self;
}
@end
Then, any code that needs to interact with the database can retrieve the queue like so:
FMDatabaseQueue *queue = DatabaseManager.sharedManager.queue;