1

I am creating a class for my iOS application that deals with all my database functions. I have a single class method, for now, that saves data. In it, I create a database and a table. Then, I begin to save data. This happens every time this method is called. However, I am only dealing with a single database with a single table, so I want all this to happen only once.

#import <sqlite3.h>
#import "LocalDatabase.h"

@implementation LocalDatabase

+ (void)saveData:(id)sender {
    /* create database (if it doesnt exist) */
    sqlite3 *database = nil;
    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"db.sql"];
    if(sqlite3_open([path UTF8String], &database) != SQLITE_OK) {
        NSLog(@"Failed to open database");
    }

    /* create table (if it doesnt exist) */
    char *err;
    NSString *statement = @"CREATE TABLE IF NOT EXISTS DATA (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, CATEGORY TEXT)";
    if (sqlite3_exec(database, statement, NULL, NULL, &err) != SQLITE_OK) {
        NSLog(@"Failed to create table");
    }

    /* save data */
    // some code that uses database
}

@end

I do not know much about how static variables work in Objective, but I think the following code is correct:

#import "A.h"

@implementation A

static NSString *b = @"TEXT!!";

+ (void) c {
    //do stuff with b
}

@end

I believe this assigns @"TEXT!!!" to the NSString *b only once. I thought I could use this to solve my problem. However, I realized that the following does not compile:

#import "A.h"

@implementation A

static NSString *b = [NSString stringWithFormat:@"%@",@"TEXT!!!"];

+ (void) c {
    //do stuff with b
}

@end

This means that I cannot make a method call in the assignment that way. Also, I cannot have if-statements and such. Is there any way I can do these only once the same way it does static NSString *b = @"TEXT!!"; only once? Of course I can create a seperate class method just for initialization or create a static boolean that keeps track of whether I have initialized yet, but I was wondering if there is a cleaner way.

golddove
  • 1,165
  • 2
  • 14
  • 32

2 Answers2

3

Use something like this inside of your method:

static dispatch_once_t pred;
dispatch_once(&pred, ^{
    // stuff to do only once
});
Logan
  • 52,262
  • 20
  • 99
  • 128
  • I will try this out, thanks! Looking at the [documentation](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once) for it. – golddove Mar 23 '14 at 19:57
  • Do I have to call this specifically for the class? If so, how is it different from just creating my own class method for initialization? `+(void)initializeDatabaseAndTable;`? – golddove Mar 23 '14 at 20:01
  • 1
    Here's what I think is a pretty good explanation: http://stackoverflow.com/a/9119089/2611971 – Logan Mar 23 '14 at 20:07
  • My bad, I did not realize that `dispath_once` was something I put inside my method. I thought it was its own method. – golddove Mar 23 '14 at 20:07
  • Ahhh ... I'll update my answer for clarity just in case someone else comes across it! – Logan Mar 23 '14 at 20:08
  • Thanks! Also, can I put `static dispatch_once_t pred;` outside of the method the same way I did `static NSString *b = @"TEXT!!";` in my example? If so, does it make a difference? Sorry, I am new to Objective-C. – golddove Mar 23 '14 at 20:12
  • 1
    If you put it outside of the method, it will still "work", but I don't think it's a good idea. Doing that makes the `pred` variable accessible from any method in your .m. If you only have one method that uses a variable named `pred` you might not notice a problem right away, but you wouldn't be able to repeat this code. On the basis of these potential complications, and the fact that it's unnecessary, I'd say stick it inside the scope of the method. – Logan Mar 23 '14 at 20:18
  • Oh ok, thanks! I also just found [+initialize](http://stackoverflow.com/questions/13326435/nsobject-load-and-initialize-what-do-they-do). Would this also work for my purposes? – golddove Mar 23 '14 at 21:02
  • Here's a quote from the last answer on the page you sent "BTW, another issue to consider with +initialize is that if someone subclasses you, you'll potentially get called once for your class and once for each subclass. If you're doing something like setting up static variables, you'll want to guard against that: either with dispatch_once() or by testing self == [MyClass class]." No matter what, you should run dispatch_once() if you want to make sure something runs once and only once. – Logan Mar 23 '14 at 21:09
0
+ (instancetype)sharedDatabase {
   static LocalDatabase *_sharedDatabase = nil;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
    _sharedDatabase = [[self alloc] init];
   });

  return _sharedDatabase;
}

it will return a instance for your class LocalDatabase.

dispatch_once reference

Pawan Rai
  • 3,434
  • 4
  • 32
  • 42
  • I want to use only class methods. I do not want to initialize `LocalDatabase`. – golddove Mar 23 '14 at 19:49
  • then remove the init method & put your code you want to run for once. – Pawan Rai Mar 23 '14 at 19:51
  • In my question: "Of course I can create a seperate class method just for initialization ... but I was wondering if there is a cleaner way." By initialization, I meant the code that I want to run for once. – golddove Mar 23 '14 at 19:54