How To Preserve User Data Using SQLite or Core Data on Apple Devices

Tyler Herman
Share

When building an iOS application, one thing to consider is a concept called “data persistence.” Data persistence is a method of holding onto data in case the application or device is reset. No one wants to be playing a game or updating their checkbook and lose everything after a reset because a poorly-designed app did not store their information. There are many ways to ensure that user data is captured and preserved, but the two methods that I want to highlight are using SQLite databases and using Apple’s Core Data framework.

Apple has embedded functionality to use SQLite databases within your application. This method is exclusively for SQLite databases, and not mySQL databases. Whats the difference? The main difference between the two is that SQLite databases are embedded in the application, and they’re one standalone file. MySQL databases take up much more space due to their dependence on multiple files, and they cannot be embedded within an application. You’ll need a special editor to view these SQLite databases. I use and recommend SQLVue Lite. There is also a full version that costs more and offers a few extra features.

Apple’s Core Data framework, unlike SQLite, takes more of an object-oriented approach to storing user data. With Core Data, you create entities that resemble classes and objects to store data. These entities are held in a data model, which is used to store various properties for the data. You can store attributes that resemble instance variables in Objective-C, relationships between objects and other data, as well as fetched properties, which are very similar to the aforementioned relationships. The only difference between relationships and fetched properties is that relationships pull all data preemptively with the object, while fetched properties recognize the existence of related data, but do not pull it right away.

Before we define the exact differences between each method, let’s check out some example code that would be used for each technique. Comparing the two code samples will give us a better idea of the differences between the two methods.

SQLite

As you might imagine, this method requires the use of SQL statements. If you are not familiar with those, it may help to take a look at how these are structured before getting too deep. Some good documentation can be found here.

(Note: All examples presented are from XCode 4.2)

Before we get started, we need to include the dynamic library that will provide all of the necessary functions for our data storage. Locate your iOS SDK on your system and go to usr/lib/libsqlite3.dylib.

To access a database, we first have to open the connection to it within the code. This part is surprisingly easy. The code simply looks like this:

sqlite3 *database;

int result = sqlite3_open(“/path/to/database/file”, &database);

Then, when you are done with it, you close the statement.

sqlite3_close(database);

There is no reason to open and close a database without making any meaningful changes. The whole point of this is to make use of the database, so lets take a look at the sqlite3_exec command. There is a bit of a trick to this. You would think that you’d use this command in all cases, but that is not true. This is only viable for SQL statements that do not return any data. This would include DQL commands like CREATE, INSERT, UPDATE, DELETE, etc.

Lets take a look at two different methods to retrieve data using a SELECT statement. The first method is fairly basic, but it is a little more complicated than the equivalent statements would be if passed through sqlite3_exec.

NSString *query = @”SELECT ID, FIELD_DATA FROM FIELDS ORDER BY ROW”;

sqlite3_stmt = *statement;

int result = (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil);

while(sqlite3_step(statement) == SQLITE_ROW){

int rowNum = sqlite3_column_int(statement, 0);

char *rowData = (char *)sqlite3_column_text(statement, 1);

NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];

// do something with the data

[fieldValue release];

}

sqlite3_finalize(statement);

There are a few important steps in the above markup. First, we create our SQL statement. Then, we have to prepare the database to be utilized. When looking at the sqlite3_prepare_v2 function, we see that the first parameter is the database variable that we created. Then, we take our query string and turn it into a UTF8 based C string. Finally, we have our return statement. We have the possibility to store any unused portions of the query, but we set it to nil.

Once we have those statements in place, we start cycling through all of the results using the sqlite3_step(statement) and making sure each becomes a row within the database. The rest is fairly basic. We first figure out which row we are on and what data it holds, and we store it in an NSString object that we can then use.  This is the part where you can get creative with the data. A very common tactic is storing the data as an NSDictionary or NSArray to be used when displaying lots of information, or when using a table view.

The other method I mentioned is conceptually the same, but we can use variables in the statement to grab specific information. There is a slight difference, and for more practice, let’s use an INSERT statement and therefore the sqlite3_exec command.

char *sql = “insert into foo values (?,?);”;

sqlite3_stmt *stmt;

if(sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK){

sqlite3_bind_int(stmt, 1, 235);

sqlite3_bind_text(stmt, 2, ”bar”, -1, NULL);

}

if(sqlite3_step(stmt) != SQLITE_DONE){

NSLog(@”This should be real error checking!”);

}

sqlite3_finalize(stmt);

The processing methods between the two techniques differ in several ways. Once again, we start by creating the query and the statement handle. Note that in the query string there are two question marks. These will end up being the values after we bind them. To bind them, we use the sqlite3_bind commands, adding one integer and one text value. The first parameter is the statement handle, then where we want to put it (first question mark is 1, second is 2) followed by the value.

The rest of the SQLite statements should look very familiar, so we won’t restate the process. As a whole, that should give you a good idea of how to use SQLite in your applications. Now it is time to look at Core Data.

To start with Core Data you need to create a data model which can be created by clicking “File” -> “New” -> “New File.” Then, under iOS on the left, select “Core Data” and then “Data Model.”

Within this new file, you create your entities using the button on the bottom left of the view. Go ahead and create one. I just left mine as “Entity.” Now that we have the beginnings of Core Data, we can start digging into the code.

Start by importing the framework CoreData from wherever you have your iOS SDK on your machine, and add <CoreData/CoreData.h> the AppDelegate.h within your application. Also, import AppDelegate.h into your ViewController.m. Then in ViewController.m make the following change to the ViewDidLoad function:

- (void)viewDidLoad

{

AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

NSManagedObjectContext *context = [appDelegate managedObjectContext];

NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@&quot;Entity&quot; inManagedObjectContext:context];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:entityDescription];

NSError *error;

NSArray *objects = [context executeFetchRequest:request error:&amp;error];

if(objects == nil){

NSLog(@&quot;There was an error!&quot;);

}

for(NSManagedObject *oneObject in objects){

NSNumber *lineNum = [oneObject valueForKey:@&quot;lineNum&quot;];

NSString *lineText = [oneObject valueForKey:@&quot;lineText&quot;];

NSString *fieldName = [NSString stringWithFormat:@&quot;line%d&quot;, [lineNum integerValue]];

UITextField *theField = [self valueForKey:fieldName];

theField.text = lineText;

}

[request release];

}

This method above does something that you don’t see very often in any application. Within the view controller, we are referencing the instance of the application delegate to get some base functionality for core data. The next few lines need a bit more explanation.

NSManagedObjectContext *context = [appDelegate managedObjectContext];

NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@&quot;Entity&quot; inManagedObjectContext:context];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:entityDescription];

Core data works within a context. The context is used to reference where all of the entities are stored. Then, we grab a description of our entity (named “Entity”) within that context we defined. Finally, we simply request all of the attributes, relationships, and fetched properties of that specific entity.

Just like with SQLite3, we then cycle through all of the values in the object and assign them to variables to be used later. There is obviously a lot more that can be done with this technology, but these serve as basic examples to gain an understanding of the differences between the two methods.

Before running this code, make sure that you add the following to AppDelegate.h.

NSManagedObjectContext *managedContext_;

NSManageObjectModel *managedObjectModel_;

NSPersistentStoreCoordinator *persistentStoreCoordinator_;

Also, be sure to add properties for these and synthesize them in the implementation file.

So, what are the key difference between the two? The SQLite method was fairly complicated with the syntax, and it needed all of the string values to be C-based with the UTF8 format. On advantage to SQLite is that files can be easily viewed to see the relationships between different data. Conversely, the Core Data method is based on Objective-C, and stores data as manageable objects. This allows for a friendly interface to create objects and add properties. There is some more code involved, and using Core Data could potentially result in a larger file size compared to smaller SQLite databases, but it makes it very easy to fetch and determine what code needs to run in order to retrieve the data.

Is one better than the other? Not necessarily. I personally prefer to use Core Data, since it does all of the dirty work for you. But, if you need a lightweight, streamlined app, or if you’re working with a very small amount of storage, SQLite may suit your purposes better.