Search code examples
iosmultithreadingsqlitefmdb

can fmdatabaseQueues be accesed from other classes


I have an app that used FMDatabase to insert data in collected by the user and some downloaded from the web. I am currently getting some crashed due to too many simultaneous requests to the database.

I want to go through my app and add FMDatabaseQueue to all of my db operations, however I need one queue for the whole app as I have have background classes that download data from the web and insert it in the db and I have to access the database in order to populate the UITableView that the user sees.

So my Question is can you make a static FMDatabaseQueue that is referenced in all classes?

My second question is if my queries are currently of the format of;

 FMResultset *result=  [[databaseModel sharedInstance]executeQuery:@"SELECT * FROM TABLE1"];
if(![result next]){ 
       [[databaseModel sharedInstance]executeUpdate:@"UPDATE TABLE1 SET foo=bar where 1;"];

does this then become ?

[dbQueue inDatabase(FMdatabase db) ^{ //dbQueue is declared statically
    FMResultset *result=  [[databaseModel sharedInstance]executeQuery:@"SELECT * FROM TABLE1"];
    if(![result next]){ 
           [[databaseModel sharedInstance]executeUpdate:@"UPDATE TABLE1 SET foo=bar where 1;"];
    }
}];

Any advice, pointers to further reading or blogs would be greatly appreciated, thanks in advance mark


Solution

  • There are a couple of ways to make your FMDatabaseQueue accessible via different classes. Either a singleton, make it a property of your app delegate (which you can retrieve by retrieving the app's delegate from [[UIApplication sharedApplication] delegate]), or create in your first view controller and pass it around.

    Personally, I'd lean towards the singleton object, say DatabaseManager. The @interface file might look like:

    //
    //  DatabaseManager.h
    //  Database Manager Example
    //
    //  Created by Robert Ryan on 6/24/13.
    //  Copyright (c) 2013 Robert Ryan. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    #import "FMDatabaseQueue.h"
    
    @interface DatabaseManager : NSObject
    
    @property (nonatomic, strong) FMDatabaseQueue *databaseQueue;
    
    + (instancetype)sharedManager;
    
    @end
    

    The @implementation might look like:

    //
    //  DatabaseManager.m
    //  Database Manager Example
    //
    //  Created by Robert Ryan on 6/24/13.
    //  Copyright (c) 2013 Robert Ryan. All rights reserved.
    //
    
    #import "DatabaseManager.h"
    
    @implementation DatabaseManager
    
    + (instancetype)sharedManager
    {
        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) {
            _databaseQueue = [[FMDatabaseQueue alloc] initWithPath:[self databasePath]];
        }
        return self;
    }
    
    - (NSString *)databasePath
    {
        NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
        return [docsPath stringByAppendingPathComponent:@"test.sqlite"];
    }
    
    @end
    

    Then, when you want to use this singleton, include DatabaseManager.h header file, you could do something like:

    #import "ViewController.h"
    #import "DatabaseManager.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        DatabaseManager *databaseManager = [DatabaseManager sharedManager];
    
        [databaseManager.databaseQueue inDatabase:^(FMDatabase *db) {
            FMResultSet *result = [db executeQuery:@"SELECT * FROM TABLE1"];
            if (!result) {
                NSLog(@"%s: executeQuery error: %@", __FUNCTION__, [db lastErrorMessage]);
                return;
            }
    
            while ([result next]) {
                // do whatever you want with the results
            }
    
            [result close];
        }];
    }
    
    // the rest of your code here
    
    @end
    

    In short, you want to retire your databaseModel singleton (BTW, your class names should, by convention, begin with an uppercase letter), and create a singleton for the FMDatabaseQueue object. Then, inside the inDatabase block, you can just refer to db parameter, not referencing any external database object.

    Note, I've made my singleton a NSObject subclass, and the FMDatabaseQueue is a public property of that class. You have a bunch of alternatives here. You could, for example, also make the singleton a FMDatabaseQueue subclass itself (kind of like you apparently did with your current databaseModel object, which appears to be a FMDatabase subclass). Alternatively, and my personal preference, I actually make the FMDatabaseQueue object a private property of my DatabaseManager and excise any FMDB code from my controllers, etc. I keep all of the FMDB code in either this new singleton or, possibly, in my model objects. When you get big hairy projects, you probably don't want SQL statements littered all over the place. Personally, I think the FMDB code and SQL statements is an implementation detail that should be encapsulated in the DatabaseManager class, or possibly model classes. It makes the management of huge projects a little easier that way. But you can do it anyway you want.