Open ilanl opened 11 years ago
In answer to your question of your title, you don't, technically, perform SELECT
and UPDATE
statements concurrently. You use FMDatabaseQueue
to (a) ensure the database operations happen sequentially; but (b) to coordinate these database requests being issued from concurrent threads. For examples, see the FMDatabaseQueue documentation.
A couple of observations:
fmdb
variable? The _queue
passes you a db
variable which you should be using for your database interaction. Your app should really have just the one FMDatabaseQueue
instance, and no other instances of FMDatabase
.UPDATE
/INSERT
statements if you use transactions. See inTransaction
._writeQueueLock
. One of the advantages of using queues as it obviates the need for locks (if that queue is serial, i.e. a maxConcurrentOperationCount
of 1
, which your update queue should be). Clearly, if you have some other operations that you need to synchronize with your _writeQueue
, you can use locks, but then again, you could just add those other operations to the _writeQueue
, and you'd enjoy full synchronization without the use of locks.stringWithFormat
for all of those parameters to the executeUpdate
, which is unnecessary if all of your properties are, themselves, objects (which I infer to be the case from your use of the @"%@"
format string). It's better to just use the property objects directly. If those might be nil
, then add a check to pass [NSNull null]
if it's nil
.nil
to the end of the executeUpdate
. That's not necessary (and possibly problematic).Thus, I might suggest:
// DbAdapter.m
#import "DbAdapter.h"
#import "FMDatabaseQueue.h"
#import "FMDatabase.h"
#import "Forecast.h"
#import "ForecastResponse.h"
@interface DbAdapter ()
@property (nonatomic, strong) FMDatabaseQueue *queue;
@property (nonatomic, strong) NSOperationQueue *writeQueue;
@end
@implementation DbAdapter
+ (instancetype)sharedAdapter
{
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init
{
self = [super init];
if (self) {
_writeQueue = [[NSOperationQueue alloc] init];
_writeQueue.maxConcurrentOperationCount = 1;
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:@"weather.sqlite"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path];
// if you want to copy the database from the bundle if the file doesn't exist, do that here
//
// if (!fileExists) {
// // copy database from bundle
// }
_queue = [[FMDatabaseQueue alloc] initWithPath:path];
// alternatively, if you want to build the database dynamically, do that after opening/creating the database
//
// if (!fileExists) {
// [self createForecastTable];
// }
}
return self;
}
- (void)saveForecastsToLocal:(ForecastResponse*) response
{
[_writeQueue addOperationWithBlock:^{
[_queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
BOOL success = [db executeUpdate:@"DELETE FROM Forecast"];
NSAssert(success, @"delete failed: %@", [db lastErrorMessage]);
for (Forecast* f in response.forecasts)
{
success = [db executeUpdate:@"INSERT INTO Forecast(Day,Hour,TempC,WaterTempC,WaveH,Weather,SwellSecs,WindDir,WindF) VALUES(?,?,?,?,?,?,?,?,?)",
f.day ?: [NSNull null],
f.hour ?: [NSNull null],
f.tempC ?: [NSNull null],
f.waterTempC ?: [NSNull null],
f.waveH ?: [NSNull null],
f.weather ?: [NSNull null],
f.swellSecs ?: [NSNull null],
f.windDir ?: [NSNull null],
f.windF ?: [NSNull null]];
NSAssert(success, @"insert failed: %@", [db lastErrorMessage]);
}
}];
}];
}
@end
My app is saving the data at once on concurrent threads to the server. Each response is then parsed and there's a saving to db
I have moved all the previous SQLite C code to use the FMDB wrapper, but still FMDB is busy: The FMDatabase <FMDatabase: 0x17d4b950> is currently in use
I'm quite new to objective C , but an example of GCD using FMDatabaseQueue would be greatly helpful:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
---- (1) this is the save to db rountine performed on response completion Inside DbAdapter singleton:
-(void)saveForecastsToLocal:(ForecastResponse*) response { [_writeQueue addOperationWithBlock:^{