Wednesday, March 25, 2009

Easy iPhone Data Storage using SQLitePersistentObjects

In a previous article, I briefly discussed one of my favorite supplemental iPhone development libraries, SQLite Persistent Objects (SPO). SPO provides object-relational persistence for the iPhone in a manner similar to what Hibernate provides for the Java crowd. For simple applications that don't need overly complicated SQL statements, SPO makes getting a functional application written much quicker. This article is part 1 of a 2-part series on SPO that I will be doing. Part 1 focuses on getting started with SPO, including setting up your project and database. Part 2 will focus on some of the additional functionality that SPO will add to your data objects and how you can take advantage of them in your application.

1. Getting the SPO source

The SPO project page () is the starting point for working with this library. I encourage you to take a look at the Wiki to get an idea of how the library works and what it is capable of. While there is a snapshot available for download, the easiest way to get a copy of the source is to download it via Subversion. Go to the Source tab on the project page for more information on the SPO repository.

Grab a copy of all of the SPO classes and add them to your project. For a clearner project structure, I tend to group these classes together to keep them out of the way.

2. Configure your project

Before we can compile and use the library, we need to add the SQLite framework to our project. SQLite is built in to the SDK, so it's just a matter of referencing it within our project. To do this, right-click on the Frameworks folder in XCode and select Add->Existing Framework. The framework you should be looking for is named libsqlite3.0.dylib.

That's all there is to it. We should now be able to begin our database development using SPO.

3. Setup your database

SPO will need to locate a database that is part of your project. It will not create an empty one for you. Luckily, creating a database is very simple if you have installed XCode correctly. Simply open a terminal window and type:

sqlite3 .sqlite

This will create the new database in your current working directory. To add this file to your project, simply drag and drop it into the Resources folder within your project.

Whether you're working with SPO or not, you need to make sure that the database is properly setup and accessed at the runtime of your application. Apple's SQLiteBooks example gives boilerplate code that you can copy and paste right into your AppDelegate class. This code will determine if a database exists for the application and, if not, create one for you by copying the default database out of your Resources folder. We have made a couple of small changes to ensure that SPO is also notified of your database location. Simply paste the following code into your AppDelegate class to get started:


- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@".sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) {
[[SQLiteInstanceManager sharedManager] setDatabaseFilepath:writeableDBPath];
return;
}
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@".sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
}
[[SQLiteInstanceManager sharedManager] setDatabaseFilepath:writeableDBPath];
}


Make sure you replace ".sqlite" with the appropriate name before compiling. Once this is in place, add the following line to your applicationDidFinishLaunching method to setup the database:


[self createEditableCopyOfDatabaseIfNeeded];


SPO and your database are now ready for business.

4. Create your data object

With our database setup out of the way, we can now begin working with SPO. We will create a couple of simple data model classes to demonstrate how SPO provides object persistence. If you choose to implement this code in your project, make sure you define your @property statements correctly. We have omitted them for sake of time. Our data model interfaces look like this:


@interface PersonModel : SQLitePersistentObject {

NSString *name;
NSString *nickname;
NSDate *dateOfBirth;

AddressModel *address;
}



@interface AddressModel : SQLitePersistentObject {

NSString *street;
NSString *city;
NSString *state;
NSString *zipCode;
}


You'll notice that all we had to do to SPO-enable our data model classes was to extend SQLitePersistentObject. This will take care of the primary key, as well as provide all of the helper methods we will make use of later on.

Now that our models are setup, we want to save an entry to our database. We can create our PersonModel object like this:


PersonModel *p = [[PersonModel alloc] init];
p.name = @"Joe Blow";
p.nickName = ...
p.date = ...

AddressModel *a = [[AddressModel alloc] init];
a.street = @"123 Main St."
...

p.address = a;


To save the PersonModel object to the database, all we need to do is call:


[p save];


That's it. No need to worry about opening a connection or any other SQL statements necessary. Our AddressModel object that is associated with the PersonModel object will also be saved with this call. There is no need to save it with a separate call. To load all of the PersonModel objects stored in the database, we simply call:


NSArray *people = [[PersonModel class] allObjects];


This will return an NSArray containing all of the PersonModel objects and their associated AddressModel objects.

Conclusion

As you've seen, SQLite PersistentObjects makes SQLite development on the iPhone much quicker. Stay tuned for Part 2, where we'll investigate some more advanced functionality that SPO provides, including queries and indices.

2 comments:

  1. I've just found the command:
    sqlite3 .sqlite

    didnt generate the db.

    I had to do:
    sqlite3 mydatabase
    create table test( one varchar(10), two smallint );
    .quit

    Then the database was created.

    ReplyDelete
  2. Great stuff! You have one typo in the provided setup code though:

    Twice you reference a variable called writeableDBPath when in fact it should be writableDBPath. (difference is the 'e')

    ReplyDelete