Thursday, March 26, 2009

Easy iPhone Data Storage using SQLitePersistentObjects, Part 2

Yesterday, we discussed SQLite Persistent Objects (SPO) and how it can make data persistence quicker and easier in your iPhone application. We covered the basics of how to setup your database, setup your data model objects, and perform basic save/retrieval on those objects. Today, we'll go over some of the more advanced functionality that SPO provides, and how you can leverage it in your application.

For our discussion, we will reuse the data model objects we defined yesterday:


@interface PersonModel : SQLitePersistentObject {

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

AddressModel *address;
}



@interface AddressModel : SQLitePersistentObject {

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


1. Querying by Property

When you persist an object using SPO, dynamic class methods are automatically added, allowing you to query by any persisted property. This reduces the amount of SQL code you would have to write, since you no longer need to write your own "SELECT * FROM ..." statements. For example, if we wanted to get a list of all of the people in our database who were named "John Smith", we could write:


NSArray *smiths = [PersonModel findByName@"John Smith"];


That's all there is to it. We can query by any property that is persisted by calling the findByXXX method.

2. Querying by Criteria

What if we wanted to retrive objects using arbitrary criteria? SPO provides methods for that, too. The SQLitePersistentObject class includes a method findByCriteria that allows us to specify our search criteria. For example, to locate all people named "John Smith" that also have the nickname "Johnny", we could write:


NSArray *people = [PersonModel findByCriteria:@"WHERE name = 'John Smith' AND nickname = 'Johnny'"];


We can make this call as simple or as complex as necessary. Include equations, NULL checks, etc. The findFirstByCriteria method works in the same way, however it only returns the first object that matches the search criteria, as opposed to an NSArray of objects.

3. Indices

If we're going to be querying by name, it's important that we define indices so that our SQL queries execute quickly. We can add indices by overriding the indices class method in our data model objects. To add an index for the name property, we would simply write:


+(NSArray *) indices {
NSArray *index1 = [NSArray arrayWithObject:@"name"];
NSArray *indices = [NSArray arrayWithObject:index1];

return indices;
}


This method returns an NSArray comprised of NSArrays that contain the properties that should be used in building the index. Once this is added, our name property will be indexed, and we should see a performance increase when dealing with larger datasets.

4. Transient fields

What if there are properties we do not want persisted? SPO also provides a mechanism for marking properties as transient, thereby not including them within the database. To mark a field as transient, we need to override the transients method in our data model objects. To make the nickname property transient, for example, we would write:


+(NSArray *)transients {
return [NSArray arrayWithObject:@"nickname"];
}


If you examine your database structure after making this change, you will see that the nickname property is no longer stored in the database. This is useful when you wish to make calculations or other values accessible as properties, but want to obtain them at runtime, rather than from the database.

5. Determine if Objects Exist in the Database

The existsInDB method allows us to determine if our data model object has been persisted to the database. For example:


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

BOOL saved = [p existsInDB]; // Should return NO

[p save];

BOOL savedYet = [p existsInDB]; // Should return YES


This convenience method allows us to determine if our changes have been saved already.

6. Object Deletion

SPO provides 2 methods for deleting objects: deleteObject and deleteObjectCascade. The deleteObjectCascade also allows us to specify whether the child rows associated with the deleted object should also be deleted.

7. Reverting Changes

Often, it is necessary to revert changes that have been made to an object, but not saved to the database yet. SPO provides us with 3 methods for performing an "undo", of sorts. These are: revert, revertProperty, and revertProperties. The revert method will revert all changes made to an objects since the last save to the database. The revertProperty and revertProperties allow us to revert changes to one or more properties, respectively, by name.

This concludes our 2-part introductory lesson on SQLitePersistentObjects. If the rumors are true, iPhone 3.0 will include support for CoreData for data persistence, so it remains to be seen how much of this will be necessary once most users move to iPhone 3.0. In the meantime, however, these tips should help you get your application developed quicker and with far fewer headaches.

2 comments:

  1. Thanks for article.

    Have a question - does SPO support migrations? Is there an easy way to change database structure from version to version?

    ReplyDelete
  2. @Valerii Hiora

    I'm not entirely sure, actually. I would imagine that if the change is minor (adding/removing a column/field, etc.) then the schema would be updated. As far as migrations, you would have to pose that question to the developer.

    ReplyDelete