3. Contacting Me
• jeff_lamarche@mac.com
• http://iphonedevelopment.blogspot.com
• Twitter: jeff_lamarche
Wednesday, March 11, 2009
4. About Me
• Writer
• Beginning iPhone Development (Apress)
• Several Articles for ADC & MacTech Mag
• Beginning Cocoa (Apress, in progress)
• Regular blog postings
Wednesday, March 11, 2009
5. More About Me
• Programmer
• Primarily contract work
• Using Cocoa since about 2000
• OO Programming since early 1990s
• Programming since 1980
• Full-time with Cocoa for only a year
Wednesday, March 11, 2009
9. SQLPO
SQLitePersistentObjects
• Object-Relational Mapping (ORM) Tool
or for the more technically-minded:
code that takes data from your program and
sticks it into or pulls it out of a relational
database
Wednesday, March 11, 2009
10. In the Beginning...
• Early OO programs that needed to use data
from a database embedded SQL statements
in code
• Difficult to maintain
• Strings are a “black box” to the compiler
Wednesday, March 11, 2009
11. ORM History
• 1996 Enterprise Object Frameworks (EOF)
• Part of NeXT’s Cocoa-Based WebObjects
• Used visual tool to map data objects to
database table
• Once model created, you interacted with
database tables as if they were objects
• Didn’t even have to create specific classes
if you didn’t want to
Wednesday, March 11, 2009
12. ORM History
• Basic idea was borrowed by other platforms,
changed to work with other languages,
sometimes extended to be better
• Hibernate, Cayenne, ADO .net, Outlet,
Django, many others
Wednesday, March 11, 2009
13. ORM History
• One language evolved the concept
• Ruby on Rails’ ActiveRecord
• No longer needed mapping document
• Object structure dynamically created
based on table structure in database
Wednesday, March 11, 2009
14. ORM History
• Core Data
• Core Data on the Mac is EOF’s stepchild
• Core Data is NOT EOF, but shares some
DNA
Wednesday, March 11, 2009
15. ORM History
Apple has not ported Core Data to the
iPhone (yet).
That fact is what triggered the birth of
SQLitePersistentObjects
Wednesday, March 11, 2009
16. SQLPO
SQLitePersistentObjects
• You can always find the latest version here:
http://code.google.com/p/sqlitepersistentobjects/
• Add the code from the /src directory to your
Xcode project
• Make sure to link to the SQLite3 dynamic library
at /usr/lib/libsqlite3.dylib
Wednesday, March 11, 2009
17. SQLPO
SQLitePersistentObjects
• We have a support mailing list:
http://groups.google.com/group/sqlitepersistentobjects-user
• Licensed under the New BSD License
• Free, open source.
• No attribution required, non-viral, can be used in
commercial projects, no need to publish your code
• If you can’t use BSD licensed code for some reason,
contact me - we’re flexible on the license.
Wednesday, March 11, 2009
18. The Basics
SQLitePersistentObjects
• To create a persistent object, you simply subclass
an existing class.
#import <Foundation/Foundation.h>
#import quot;SQLitePersistentObject.hquot;
@interface Person : SQLitePersistentObject
{
NSString *firstName;
NSString *lastName;
NSDate *birthdate;
int numberOfChildren;
float contribution;
UIImage *photo;
}
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSDate *birthdate;
@property int numberOfChildren;
@property float contribution;
@property (nonatomic, retain) UIImage *photo;
@end
Wednesday, March 11, 2009
19. The Basics
SQLitePersistentObjects
• To create a persistent object, you simply subclass
an existing class.
#import <Foundation/Foundation.h>
#import quot;SQLitePersistentObject.hquot;
@interface Person : SQLitePersistentObject
{
NSString *firstName;
NSString *lastName;
NSDate *birthdate;
int numberOfChildren;
float contribution;
UIImage *photo;
}
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSDate *birthdate;
@property int numberOfChildren;
@property float contribution;
@property (nonatomic, retain) UIImage *photo;
@end
Wednesday, March 11, 2009
20. The Basics
SQLitePersistentObjects
• To create a persistent object, you simply subclass
an existing class.
#import quot;Person.hquot;
@implementation Person
@synthesize firstName;
@synthesize lastName;
@synthesize birthdate;
@synthesize numberOfChildren;
@synthesize contribution;
@synthesize photo;
- (void)dealloc
{
[firstName release];
[lastName release];
[birthdate release];
[photo release];
[super dealloc];
}
@end
Wednesday, March 11, 2009
21. The Basics
SQLitePersistentObjects
• Once you’ve defined a persistent object, creating a
new object and saving it to the database couldn’t
be easier:
Person *newPerson = [[Person alloc] init];
newPerson.firstName = @quot;Marthaquot;;
newPerson.lastName = @quot;Washingtonquot;;
newPerson.birthdate = [NSDate date];
newPerson.numberOfChildren = 5;
newPerson.contribution = 27.32;
newPerson.photo = [UIImage imageNamed:@quot;MarthaWashington.pngquot;];
[newPerson save];
[newPerson release];
Wednesday, March 11, 2009
22. The Basics
SQLitePersistentObjects
• Once you’ve defined a persistent object, creating a
new object and saving it to the database couldn’t
be easier:
Person *newPerson = [[Person alloc] init];
newPerson.firstName = @quot;Marthaquot;;
newPerson.lastName = @quot;Washingtonquot;;
newPerson.birthdate = [NSDate date];
newPerson.numberOfChildren = 5;
newPerson.contribution = 27.32;
newPerson.photo = [UIImage imageNamed:@quot;MarthaWashington.pngquot;];
[newPerson save];
[newPerson release];
Wednesday, March 11, 2009
23. The Basics
SQLitePersistentObjects
• How does it save the data?
• Raw datatypes are mapped to appropriate
columns
• e.g. int goes into INTEGER field, float goes
into REAL field
• Other persistent objects are stored as
references to the row and table where that
object is stored
Wednesday, March 11, 2009
24. The Basics
SQLitePersistentObjects
• How does it save the data? (cont)
• Objects that aren’t subclasses of
SQLitePersistentObject get stored IF they
conform to a protocol called SQLitePersistence
• We have provided categories for most common
objects to conform them to SQLitePersistence
• NSString, NSNumber, UIImage, UIColor, NSData,
NSDate, NSMutableData
Wednesday, March 11, 2009
25. The Basics
SQLitePersistentObjects
• How does it save the data? (cont)
• We also provide category on NSObject as a
fallback. Any object that doesn’t conform to
SQLitePersistence but that does conform to
NSCoding can be stored in the database, but
gets archived as a BLOB, which can’t be
searched or used in criteria-based queries.
Wednesday, March 11, 2009
26. The Basics
SQLitePersistentObjects
• How does it save the data? (cont)
• Dictionaries, Arrays, and Sets do not get stored
in a column in the object’s table, but rather get
stored in a child table.
• Each item in collection gets one row in the
child table
• Raw Datatypes and non-persistent objects get
stored right in child table
• Persistent objects get stored as references
Wednesday, March 11, 2009
27. The Basics
SQLitePersistentObjects
• How does it save the data? (cont)
• Every objects gets assigned a primary key value,
which is an integer that acts as a unique
identifier for that object
• Stored in a private instance variable called pk
• Used in some cases load objects from database
Wednesday, March 11, 2009
28. The Basics
SQLitePersistentObjects
• Loading an object from the database is
accomplished through class methods. This is the
simplest one:
Person *martha = [Person findByPK:1];
Wednesday, March 11, 2009
29. The Basics
SQLitePersistentObjects
• You can load all objects into an array. Be careful
doing this, however, as it is not a very efficient use
of memory in most cases.
NSArray *people = [Person allObjects];
Wednesday, March 11, 2009
30. The Basics
SQLitePersistentObjects
• SQLPO also creates dynamic find methods based
on your property names:
NSArray *people = [Person allObjects];
NSArray *people = [Person findByLastName:@quot;Washingtonquot;];
Wednesday, March 11, 2009
31. The Basics
SQLitePersistentObjects
• Dynamic find by methods support SQL wildcards
NSArray *people = [Person allObjects];
NSArray *people = [Person findByLastName:@quot;Washingtonquot;];
NSArray *people = [Person findByLastName:@quot;Wash%quot;];
Wednesday, March 11, 2009
34. The Basics
SQLitePersistentObjects
• If you need more flexibility, you can always specify
the exact criteria by supplying a SQL where clause
with findByCriteria:
NSArray *people = [Person findByCriteria:@quot;WHERE first_name = 'John' and last_name like
'S%' and date(birthdate) <= date('now')quot;];
Wednesday, March 11, 2009
35. The Basics
SQLitePersistentObjects
• If you need more flexibility, you can always specify
the exact criteria by supplying a SQL where clause
with findByCriteria:
NSArray *people = [Person findByCriteria:@quot;WHERE first_name = 'John' and last_name like
'S%' and date(birthdate) <= date('now')quot;];
Note: Property firstName
becomes column first_name
Wednesday, March 11, 2009
36. The Basics
SQLitePersistentObjects
• You can find out the column name for a property
name like so:
#import quot;NSString-SQLiteColumnName.hquot;
…
NSString *columnName = [@quot;firstNamequot; stringAsSQLColumnName];
Wednesday, March 11, 2009
37. The Basics
SQLitePersistentObjects
• If you only want the first object that meets your
criteria, you can do that also:
Person *firstPerson = [Person findFirstByCriteria:@quot;WHERE first_name =
'John' and last_name like 'S%' and date(birthdate) <= date('now')quot;];
Wednesday, March 11, 2009
38. The Basics
SQLitePersistentObjects
• Deleting an object should be done using the class
method deleteObject:cascade:, which takes the
primary key of the object to be deleted.
[Person deleteObject:5 cascade:YES];
Wednesday, March 11, 2009
39. The Basics
SQLitePersistentObjects
• Database tables can benefit from adding indices to
them. SQLitePersistentObjects has a mechanism
for adding indices without writing SQL. Override
this method in your class:
+(NSArray *)indices
{
return nil;
}
Wednesday, March 11, 2009
40. The Basics
SQLitePersistentObjects
• Method should return an array of arrays. Each
contained array represents one index and should
have the properties to be indexed in the order
they should be in the index.
+(NSArray *)indices
{
NSArray *firstIndex = [NSArray arrayWithObjects:@quot;lastNamequot;,
@quot;firstNamequot;, @quot;pkquot;, nil];
NSArray *secondIndex = [NSArray arrayWithObjects:@quot;birthdatequot;, @quot;pkquot;,
nil];
return [NSArray arrayWithObjects:firstIndex, secondIndex, nil];
}
Wednesday, March 11, 2009
41. The Basics
SQLitePersistentObjects
• Let’s look at our class declaration again.
#import <Foundation/Foundation.h>
#import quot;SQLitePersistentObject.hquot;
@interface Person : SQLitePersistentObject
{
NSString *firstName;
NSString *lastName;
NSDate *birthdate;
int numberOfChildren;
float contribution;
UIImage *photo;
}
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSDate *birthdate;
@property int numberOfChildren;
@property float contribution;
@property (nonatomic, retain) UIImage *photo;
@end
Wednesday, March 11, 2009
42. The Basics
SQLitePersistentObjects
• Let’s look at our class declaration again.
#import <Foundation/Foundation.h>
#import quot;SQLitePersistentObject.hquot;
@interface Person : SQLitePersistentObject
{
NSString *firstName;
NSString *lastName;
NSDate *birthdate;
int numberOfChildren;
float contribution;
UIImage *photo;
}
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSDate *birthdate;
@property int numberOfChildren;
@property float contribution;
@property (nonatomic, retain) UIImage *photo;
@end
Wednesday, March 11, 2009
43. The Basics
SQLitePersistentObjects
• Let’s look at our class declaration again.
Wednesday, March 11, 2009
44. The Basics
SQLitePersistentObjects
• Let’s talk about Paired Arrays.
• Simple Concept - Multiple Arrays
• Every array has same number of rows
• Object at same index in each array
corresponds to information about the same
object
• e.g. fifth object in one array might hold Joe’s
age, and the fifth object in the other array
might hold Joe’s last name.
Wednesday, March 11, 2009
45. The Basics
SQLitePersistentObjects
• Let’s talk about Paired Arrays (cont)
• Can have as many arrays as necessary.
• SQLPO has built-in method to return specified
paired arrays.
• It packs all the paired arrays together inside
another array
• First array in the array always contains a list of the
primary keys.
Wednesday, March 11, 2009
46. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Martha Washington
2 Joe Smith
3 Sally Ride
4 George Washington
5 Buster Keaton
Wednesday, March 11, 2009
47. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Martha Washington
2 Joe Smith
3 Sally Ride
4 George Washington
5 Buster Keaton
Wednesday, March 11, 2009
48. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Martha Washington
2 Joe Smith
3 Sally Ride
4 George Washington
5 Buster Keaton
Wednesday, March 11, 2009
49. The Basics
SQLitePersistentObjects
• This means we can load in only the information we
need to display in the table, along with the
information we need to load the full object if the
user selects it.
Wednesday, March 11, 2009
50. The Basics
SQLitePersistentObjects
• In our controller class, we’ll need mutable arrays to
hold the data.
#import <UIKit/UIKit.h>
@interface PeopleListViewController : UITableViewController
{
NSMutableArray *pks;
NSMutableArray *firstNames;
NSMutableArray *lastNames;
}
@property (nonatomic, retain) NSMutableArray *pks;
@property (nonatomic, retain) NSMutableArray *firstNames;
@property (nonatomic, retain) NSMutableArray *lastNames;
- (void)refreshData;
@end
• We also declare a method for loading the arrays.
Wednesday, March 11, 2009
51. The Basics
SQLitePersistentObjects
• Getting the paired arrays is simple enough:
- (void)refreshData
{
NSArray *array = [Person pairedArraysForProperties:[NSArray
arrayWithObjects:@quot;firstNamequot;, @quot;lastNamequot;, nil] withCriteria:@quot;where birthdate is not
nullquot;];
self.pks = [array objectAtIndex:0];
self.firstNames = [array objectAtIndex:1];
self.lastNames = [array objectAtIndex:2];
}
• Just tell it which properties you want, and it will
give you all those plus the primary keys.
Wednesday, March 11, 2009
52. The Basics
SQLitePersistentObjects
• In our Table View Data Source, we just get a count
of one of the arrays so we know the number of
rows we need in our table:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:
(NSInteger)section
{
return [pks count];
}
Wednesday, March 11, 2009
53. The Basics
SQLitePersistentObjects
• We can then use the information from the arrays
to populate our table:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @quot;Cellquot;;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier] autorelease];
}
NSUInteger row = [indexPath row];
cell.text = [NSString stringWithFormat:@quot;%@, %@quot;,
[lastNames objectAtIndex:row],
[firstNames objectAtIndex:row]];
return cell;
}
Wednesday, March 11, 2009
54. The Basics
SQLitePersistentObjects
• When the user selects a row, we grab the primary
key for the selected row, and use that to load the
Person:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int thePk = [[pks objectAtIndex:[indexPath row]] intValue];
Person *thePerson = (Person *)[Person findByPK:thePk];
// Do something with the person here...
}
Wednesday, March 11, 2009
55. The Basics
SQLitePersistentObjects
Easy Enough, but there’s a problem.
What if you want to sort the arrays?
Wednesday, March 11, 2009
56. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Martha Washington
2 Joe Smith
3 Sally Ride
4 George Washington
5 Buster Keaton
Wednesday, March 11, 2009
57. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Buster Keaton
2 George Ride
3 Joe Smith
4 Martha Washington
5 Sally Washington
Wednesday, March 11, 2009
58. The Basics
SQLitePersistentObjects
• Categories to the Rescue!
• Just call sortArrayUsingSelector:withPairedMutableArrays:
[lastNames sortArrayUsingSelector:@selector(compare:)
withPairedMutableArrays:firstNames, pks, nil];
• Now all three arrays are sorted based on the last
name of the people represented in the arrays.
Wednesday, March 11, 2009
59. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
1 Martha Washington
2 Joe Smith
3 Sally Ride
4 George Washington
5 Buster Keaton
Wednesday, March 11, 2009
60. The Basics
SQLitePersistentObjects
Example: Three NSArrays
pks firstNames lastNames
5 Buster Keaton
4 George Washington
2 Joe Smith
1 Martha Washington
3 Sally Ride
Wednesday, March 11, 2009
61. The Not-So-Basics
SQLitePersistentObjects
• SQLite makes aggregations easy (if you know SQL)
double averageAge = [Person performSQLAggregation:
@quot;select avg(date('now') - date(birthdate)) from people where birthdate is not nullquot;];
Wednesday, March 11, 2009
62. The Not-So-Basics
SQLitePersistentObjects
• But, if you don’t know SQL… you’re not totally out
of luck. SQLPO creates dynamic methods for
common aggregations based on your objects’
properties:
NSNumber *average = [Person averageOfContribution];
NSNumber *washAverage = [Person averageOfContributionWithCriteria:@quot;where name = 'Washington'quot;];
NSNumber *sum = [Person sumOfContribution];
NSNumber *washSum = [Person sumOfContributionWithCriteria:@quot;where name = 'Washington'quot;];
NSNumber *count = [Person countOfPk];
NSNumber *min = [Person minOfContribution];
NSNumber *washMin = [Person minOfContributionWithCriteria:@quot;where name = 'Washington'quot;];
NSNumber *max = [Person maxOfContribution];
NSNumber *washMax = [Person maxOfContributionWithCriteria:@quot;where name = 'Washington'quot;];
Wednesday, March 11, 2009
63. The Not-So-Basics
SQLitePersistentObjects
• Defining how non-standard, non-persistent objects
get stored.
@protocol SQLitePersistence
@required
+ (BOOL)canBeStoredInSQLite;
+ (NSString *)columnTypeForObjectStorage;
+ (BOOL)shouldBeStoredInBlob;
@optional
+ (id)objectWithSqlColumnRepresentation:(NSString *)columnData;
- (NSData *)sqlBlobRepresentationOfSelf;
+ (id)objectWithSQLBlobRepresentation:(NSData *)data;
- (NSString *)sqlColumnRepresentationOfSelf;
@end
Wednesday, March 11, 2009
64. The Not-So-Basics
SQLitePersistentObjects
• The Instance Manager
• SQLPO has a singleton class that manages the
database instance.
• Mostly, this class is used behind-the-scenes without
any need for you to interact with it.
• But... it’s there if you need it.
Wednesday, March 11, 2009
65. The Not-So-Basics
SQLitePersistentObjects
• Getting the shared instance:
SQLiteInstanceManager *manager = [SQLiteInstanceManager sharedManager];
Wednesday, March 11, 2009
66. The Not-So-Basics
SQLitePersistentObjects
• Once you have it, what can you do with it?
• Get a reference to the database
sqlite3 *db = [manager database];
• Execute arbitrary SQL Updates:
[manager executeUpdateSQL:@quot;update foo set bar = 1quot;];
Wednesday, March 11, 2009
67. The Not-So-Basics
SQLitePersistentObjects
• Once you have it, what can you do with it? (cont)
• Find out if a table exists in the database.
BOOL exists = [manager tableExists:@quot;superheroesquot;];
• Change database configuration and do maintenance
[manager vacuum];
[manager setCacheSize:100]
Wednesday, March 11, 2009
68. Performance
SQLitePersistentObjects
• What Lies Ahead
• validation architecture & willSave/didSave/okayToSave:
• transient properties
• performance optimizations
• rollback
• refactoring to generalize code where makes sense
• ??? (ideas welcome)
Wednesday, March 11, 2009
69. Performance
SQLitePersistentObjects
• How well does it perform?
• Umm... good question.
• Can we get back to you on that?
Wednesday, March 11, 2009
And that is spelled with an “A” contrary to the sign out front.
A lot of people guard their personal e-mail for fear of spam and other unwelcome e-mail in their inbox. I’ve never been that smart. I’m somewhat virtually promiscuous. I’ve been using this e-mail address since Apple first started that service. I have had, and still have others, but this is the best way to reach me.
I do try to respond to every e-mail I get, though sometimes I do get a little overwhelmed. If you send me an e-mail, and you don’t get a response in a week, feel free to send another one. The only e-mails I ignore on purpose are spam.
The link is to my blog, focused on iPhone development from a technical standpoint, primarily. I post code, tutorials, etc., and also link to other good resources.
If you know my name, it’s probably from my writing. I do a fair amount of technical writing and there’s no better way to cement your understanding of a technology then to try and explain it to somebody else. It’s also a great way to raise your visibility in the community. Writing is what allowed me to go full-time with Cocoa / Apple technologies.
Writing is great, but the bulk of my income comes from doing contract programming work. Prior to last March, I was doing mostly Enterprise Software consulting - travel-based programming for large corporate and government entities. Not exciting, but pays the bills, and because I used to be a developer for PeopleSoft, it was the easiest type of work for me to get.
The release of the iPhone SDK and the book deal gave me the opportunity to finally jump with both feet into the Mac programming world.
This slide is just for grins & giggles. This bad-boy (well, not this particular bad-boy, but one that looked like this) was what I used to write my first line of code... in Applesoft Basic. I include this because a lot of people don’t believe me when I tell them I started coding on an Apple ][ made by Bell & Howell.
We’ve come a little way, haven’t we?
Fancy way of saying that it takes data stored in objects and stores and retrieves that data in and out of a relational database. Relational databases and OO programs use different, but comparable, paradigms.
The SQL strings were “black boxes” as far as the compiler was concerned - you could put the Gettysburg Address into a string literal, and your program would happily pass that onto the SQL server.
All software development builds on earlier stuff, so there may be earlier examples that could be thought of as forms of ORM, but many people recognize EOF as the first fully-fledged ORM tool, and it was the first one I used. WebObjects, though it’s now Java-based, back in 1996 was Objective-C based, and it cost $50,000 a license. They were able to get that because the development time with WO & EOF was so much faster than the tools commonly in use at the time - primarily manual CGI coding.
The basic idea behind EOF was that instead of writing SQL, you used a GUI tool to create mappings to a remote database. The mappings specified which objects and attributes corresponded to which tables and field. Once you created this mapping document (called an EOModel), you then interacted with the data in the database as if they were objects.
This represented a HUGE step forward.
WebObjects and EOF slowly fell to the side (yes, it still exists, but are not still widely used outside of Apple), while other development platforms took the ideas EOF pioneered, contorted them to fit the design patterns of other languages and, in some cases, extended them
ActiveRecord is, in a sense, the logical extension of EOF. Ruby shares a lot of similarities with Objective-C, including a very dynamic nature - both languages allow methods and attributes to be added at runtime. ActiveRecord got rid of the need for an intermediate modeling tool, and leveraged the dynamic nature of the language to automatically create the class attributes based on the database table structure.
When I first became aware of ActiveRecord, it was like the clouds parted and the angels sang. Seriously. If you’ve ever done integration work, you know just how cool this idea is. One of the first times I experimented with ActiveRecord, I took a 270 line Java integration program and turned it into a 27 line Ruby script thanks to ActiveRecord and Ruby’s terse syntax.
Now, we’re at a point where we have no SQL and no modeling tool. Your objects just take shape based on the contents of the database.
With the release of Tiger, Apple gave us Core Data, which is not EOF, but is very heavily influenced by it, and almost certainly shares some code. Using Core Data is very similar to using EOF, except there’s no remote database. They took the basic idea and reworked toward the needs of the workstation (laptop/desktop) application developer You design your data model visually, in a tool much like the one used to create the mappings in EOF, and by doing so (assuming you’ve selected SQLite as your storage option), it automatically creates the underlying tables in an embedded SQLite database. It’s great. But...
When we were writing the early chapters of the iPhone SDK, things were changing all the time, and it was hard to get a straight answer about what things we were used to having in Cocoa would be available on the iPhone. At first, Interface Builder wasn’t there, but by Beta4, we had a mostly functioning IB.
At some point, it became clear that Core Data wasn’t going to be ported to the iPhone, at least for the initial release, even though SQLite was included on the iPhone. The iPhone documentation was recommending the use of SQLite for applications that had a lot of structured data, and Apple even posted sample code showing the “recommended way” to access SQLite in your applications - the old fashioned way, embedding static SQL strings in your application.
They basically wanted us to step back in time fifteen years and do it the way we did it before EOF existed.
Well, no thank you.
Here is a fully functional class declaration. Notice that we have properties that are objects, and native datatypes. That’s all no problem. It can handle most raw datatypes, and most objects.
It can’t handle non-object pointers, like void * and char *, so you should use NSString instead of C-strings, and any chunk of memory that you have in a pointer, should be put into an NSData or NSMutableData.
When it comes to objects, they are saved based on whether the implement a formal protocol. We have provided categories for most standard objects that tells SQLPO how to save those objects into the database, translate from the object to a table column, so the data is searchable. A property that is also a subclass of SQLitePersistentObjects will get stored in its own table, with a reference to that object’s row and table being stored in this object.
If there is no category (and the object is not also a subclass of SQLitePersistentObject, then it will look to see if the object conforms to NSCoding. If it does, it will archive the object into a blob.
You must define properties for anything you want persisted into SQLite. Any non-persistent variables should not have properties. That’s how SQLPO knows what to save and what not to save. Instance Variables without properties will not get saved to the database, nor will read-only properties.
Here is a fully functional class declaration. Notice that we have properties that are objects, and native datatypes. That’s all no problem. It can handle most raw datatypes, and most objects.
It can’t handle non-object pointers, like void * and char *, so you should use NSString instead of C-strings, and any chunk of memory that you have in a pointer, should be put into an NSData or NSMutableData.
When it comes to objects, they are saved based on whether the implement a formal protocol. We have provided categories for most standard objects that tells SQLPO how to save those objects into the database, translate from the object to a table column, so the data is searchable. A property that is also a subclass of SQLitePersistentObjects will get stored in its own table, with a reference to that object’s row and table being stored in this object.
If there is no category (and the object is not also a subclass of SQLitePersistentObject, then it will look to see if the object conforms to NSCoding. If it does, it will archive the object into a blob.
You must define properties for anything you want persisted into SQLite. Any non-persistent variables should not have properties. That’s how SQLPO knows what to save and what not to save. Instance Variables without properties will not get saved to the database, nor will read-only properties.
Here’s the implementation of the class - nothing special, just typical stuff. You just implement it, and the SuperClass knows how to do pretty much everything. You could, of course (and probably should) create a designated initializer, but for simplicity’s sake, I’ve left it off here.
All you do is allocate the object as you would any other object, assign values to its properties, and then call the save method.
That one line is all it takes to save this object as a row in a table in a SQLite database. You don’t need to create the database, you don’t need to create the table, you just save.
NSDictionary, NSArray, NSSet, and their mutable counterparts all follow these rules.
In addition to the properties you define, there is one more special one, called pk, which stands for “primary key”. When you know the pk (and you’ll see why you might later), loading an object is simple enough. What if you don’t know the pk value, though?
This will create an instance of your class for every row in the database. You might do this sometimes, but usually you don’t want every objects.
This method will return an array of all people with a last name that is equal to “Washington”.
You can also use SQL pattern matching - using the % or _ wildcards (percent matches any number of characters, underscore matches just one character), this code would return everybody whose last name begins with “Wash”. Note: The bottom two examples will generate a compiler warning because the compiler won’t be able to find the method. That’s logical, since it doesn’t exist - these methods are created dynamically at runtime. You can squelch the warning by creating a category on your own class in your class’ header file
There’s our category - add the declaration for any dynamic methods you use to a category like this, right in your class’ header file, and the compiler warnings go away.
If you use this method, you are responsible for providing the correct SQL. It must start with “WHERE” and you must use the correct column names. Capitalized letters in camel case become lower-case with an underscore. But, you don’t have to remember that, because there’s a category on NSString that will do it for you. You can even specify an “ORDER BY” clause if you want the results returned in a specific order.
If you use this method, you are responsible for providing the correct SQL. It must start with “WHERE” and you must use the correct column names. Capitalized letters in camel case become lower-case with an underscore. But, you don’t have to remember that, because there’s a category on NSString that will do it for you. You can even specify an “ORDER BY” clause if you want the results returned in a specific order.
Don’t guess! When in doubt about the column name, use the category - you’ll be using the same code that we use to create the tables in the first place.
There is also an instance method of the same name that can be called to delete an object from the database, but it’s use is discourage and is going to be deprecated. Calling delete on an instance is problematic, because SQLPO maintains a map of all objects that have been loaded. It does this so that if you load the same object separately in two places, both places will be referring to the same object, rather than two separate objects created from the same source data.
You should release the object before deleting it. If you don’t, the object will continue to exist in memory after being deleted, and could end up getting unintentionally saved back to the database.
For this, you should use the property name, not the column name. In this example, we’re telling SQLPO to create two indexes, one on lastname, firstname, and pk (in that order), the other on birthdate, pk.
Here is a potential problem. The iPhone does not have virtual memory in the same way that your Mac does. It won’t write volatile pages of memory to the filesystem, and it only has 128 megs to work with. For everything. That means video ram, OS, everything. So, what do you think will happen when we load 1,000 people, each with a one-megapixel photo, into memory so that we can display them in a table?
Here is a potential problem. The iPhone does not have virtual memory in the same way that your Mac does. It won’t write volatile pages of memory to the filesystem, and it only has 128 megs to work with. For everything. That means video ram, OS, everything. So, what do you think will happen when we load 1,000 people, each with a one-megapixel photo, into memory so that we can display them in a table?
Here is a potential problem. The iPhone does not have virtual memory in the same way that your Mac does. It won’t write volatile pages of memory to the filesystem, and it only has 128 megs to work with. For everything. That means video ram, OS, everything. So, what do you think will happen when we load 1,000 people, each with a one-megapixel photo, into memory so that we can display them in a table?
Yeah, crash and burn. With traditional object archiving, it would be complex to deal with this. SQLite lets us get information without loading our whole object into memory, however, and SQLPO makes it easy to do. In order to utilize this functionality, though, you need to understand a concept called “paired arrays”. If you’ve set up a settings bundle for an application and used the MultiValue fields, you have seen paired arrays in action. To do that, you provide one array with the values to be displayed, and the other with the values that will actually be stored in the user defaults system.
It may sound a little confusing, but it’s really quite simple in practice. Let’s take a look.
So, just to be clear, the first object in the pks array will hold the primary key for the person whose first name is contained in the first object of the firstNames array and whose last name is contained in the first object of the lastNames array. Same goes for the second row, the third row, etc.
In this code here, we’re loading the arrays with information about all the people whose birthdate we know. We’re getting their first name and last name so we can display it in a table, and we keep the primary keys so that if the user selects a row, we know which person to load and display.
NSMutableArray has the ability to sort its contents based on a comparison method. But, if you sort the arrays, all of a sudden, you have a problem because the fifth object in one array, no longer necessarily corresponds to the fifth object in the other arrays.
SQLPO provides a category on NSMutableArray that lets you sort an array and also sort all of its paired arrays so that they all stay in sync.
That pretty much sums up the basics. You’ve seen how to create, load, save, and delete objects and you’ve seen how to use and sort paired arrays so that you can display data from all the objects in the database with having memory problems.
Now, let’s look at some of the more involved uses of the database.
Here is how it sorts if we sort the firstNames array using sortArrayUsingSelector:withPairedMutableArrays: instead of separately sorting the three arrays.
Do you want to know the sum or average value a field? In the old days, that would have involved iterating over all your objects and keeping a running count. Not so with SQLite. SQLite will do aggregations for you without having to load objects into memory, and it’s very, very fast.
SQLitePersistentObject has a convenience method for getting aggregate values. It returns a single floating point value returned from the SQL query you supply. Here is how you might find the average age of all the people in the database, or of just a subset. There are many aggregation methods, much like you might find in Numbers or Excel, including min, max, sum, average, rounding, etc.
SQLitePersistentObjects also creates dynamic methods at runtime for getting aggregations. Currently average, sum, count, min, and max are supported, either for the entire population, or for a subset by specifying “withCriteria” and providing a SQL where clause.
Additional aggregations are coming soon, including min, max, maybe median.
Because these are dynamic methods, will get compiler warnings about them. You can squelch them with a category, the way I showed you earlier with the findBy methods.
Let’s say you’ve got an object out there that you want to get saved in the database, but you want it be searchable, not stored as a blob, but that object doesn’t conform to SQLitePersistence. What can you do?
Answer is easy - create a category to conform that class to SQLitePersistence. Here’s that protocol.
These methods define how SQLPO talks to an object to determine if it can be saved in a column, and how to actually save it.
The first method is used to indicate that this object can be stored in SQLite. Simply return YES in that method to indicate that your object should be saved.
The second method indicates the type of column that should be used. It should return TEXT, INTEGER, REAL, or BLOB.
The third method is used to ask the object if it needs to be stored in a BLOB. Now, this probably seems a little redundant, since BLOB was one of the options in the previous method, but simply for performance, it’s faster to specify a BOOL than it is to constantly be doing string comparisons. If you return YES here, you should return BLOB in the columnTypeForObjectStorage method, although SQLite is pretty cool about datatypes, and will convert as necessary.
The last four methods serve the same purpose - one is used for objects that return YES to shouldBeStoredInBlob, and the other is used if the answer is NO. If it’s going to be stored in a BLOB, you have to provide the information in an NSData to make sure it doesn’t get altered in any way. Otherwise, you can return an object. When in doubt, return an NSString, but you can also use NSNumber, or pretty much any other object that prints its value when sent the description method.
The project currently has no formal roadmap. Development is driven now by the specific needs of the project members. When one of us needs a certain bit of functionality for a project we’re working on, we propose it to the group, vet it out for possible problems, and then it gets implemented. Very informal. At some point, we plan to sit down and come up with something more formal, and more long-term, but probably won’t be for a few months, given how busy most iPhone developers are right now.
Some of the things that have been discussed include implementing a validation architecture so that objects can do more robust validation, such things as enforcing unique fields or combinations of fields, notifications to the object that a save has occurred or will occur, and possibly even giving the object the ability to conditionally cancel or prevent a save.
Transient properties are properties that don’t get saved to the database. Currently read-only properties are treated as transient, but there is beta functionality in the system to allow other properties to be specified as transient.
There’s always room for performance optimizations. There are several places in the code where there’s room to eke out more performance, and we’ll visit those once we’ve done benchmarking and have a real view of where our slowdowns are.
Rollback isn’t much of an issue for most iPhone applications, since we tend to make the user commit changes fairly frequently. However, to get wider acceptance on the Mac
Honestly, we don’t have benchmarks. We haven’t done rigorous testing or benchmarking yet. All we have is anecdotal evidence from using it in a handful of applications. So far, I have yet to see any issues with performance compared to hand-coding the SQL.
Now, we know it takes more processing power to dynamically generate SQL on the fly than it does to execute hard-coded SQL stored in string constants. That’s a fact. We also know there are places in the code that can be optimized to work faster. But, it seems to be fast enough for the vast majority of uses on the iPhone.
Honestly, we don’t have benchmarks. We haven’t done rigorous testing or benchmarks. All we have is anecdotal evidence from using it in a handful of applications. So far, I have yet to see any issues with performance compared to hand-coding the SQL.
Now, we know it takes more processing power than hard-coded SQL. That’s a fact. We also know there are places in the code that can be optimized. But, it seems to be fast enough for the vast majority of uses on the iPhone.