I have a sync method for an app I'm building storing data in SQLite locally, using the FMDatabase wrapper. When I put all the queries in one class everything works fine. However to maintain complexity I added some data controller classes for parts of the sync, but when I do so FMDatabase gives 'database locked' errors, both when I add a new connection in a data class and when I send along the initial database connection as a parameter.
Now I was thinking to add the database connection in a songleton, and was wondering if that is good practice and how I wrap FMDatabase in a singleton class. Any help on why the issue happens and what's the best way of working around this would be greatly appreciated!
I've implemented a singelton with an FMDatabase using the following code, which seems to have resolved the issue with the locked Database error. Not sure if this is good practice, but here's the code for everyone that needs a similar implementation. I used this tutorial on singeletons.
The singleton:
DatabaseController.h:
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
@interface DatabaseController : NSObject
@property (strong, nonatomic) FMDatabase *db;
+ (id)sharedDatabase;
- (void)initDB;
- (FMDatabase *)getDB;
@end
DatabaseController.m:
#import "DatabaseController.h"
@implementation DatabaseController
static DatabaseController *sharedDatabase = nil;
// Get the shared instance and create it if necessary.
+ (DatabaseController *)sharedDatabase {
if (sharedDatabase == nil) {
sharedDatabase = [[super allocWithZone:NULL] init];
}
return sharedDatabase;
}
- (void)initDB {
NSString *databaseName = @"MyDatabase.db";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
NSString *databasePath = [documentDir stringByAppendingPathComponent:databaseName];
_db = [FMDatabase databaseWithPath:databasePath];
if (![_db open])
NSLog(@"Database problem");
}
// Return database instance
- (FMDatabase *)getDB {
return _db;
}
// We can still have a regular init method, that will get called the first time the Singleton is used.
- (id)init {
self = [super init];
if (self) {
// Work your initialising magic here as you normally would
}
return self;
}
// Equally, we don't want to generate multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
@end
Using the singelton
Then everywhere I need to acces the database I use:
DatabaseController* sharedDatabase = [DatabaseController sharedDatabase];
[sharedDatabase initDB]; //only the first time! In my case in my app delegate
FMDatabase *db = [sharedDatabase getDB];