Tuesday, September 4, 2012

A Model (Object) Is A Beautiful Thing

In object-oriented programming, a common pattern is MVC: Model-View-Controller.  For some reason, the Model typically seems to be the slowest concept to grasp for new developers.  Views are easy to understand, probably because they are visual.  Controllers are maybe not 100% understood, but at least there are plenty of examples so people plod through.  Generally speaking, however, the model is often the most important component of an app, and the number of ways to handle the model badly are considerable.  This is odd, because the good ways really are not that hard.

 

Of Thee I String

 

Most developers begin their programming lives with strings.  Strings are easy and readable.  And then for more complicated structures, they move onto arrays.  Arrays are still relatively easy to understand, and aren't too hard to visualize.  They are also easy to teach, which is why books and tutorials tend to begin here.  And that's fine, we all need to start somewhere.  Let's look at a simple list of colors:

 

NSArray *colorList = [NSArray arrayWithObjects:@"Blue", @"Green", @"Red", nil];

 

Straightforward, easy to read, a good starting point.  If we were discussing table views, then it would be simple to take this array and use it to drive a table view.  That's more than sufficient for static display, and helps to establish concepts.  Another example:

 

NSArray *actors = [NSArray arrayWithObjects:@"Brad Pitt", @"William H. Macy", @"Jennifer Aniston", nil];

 

Still just strings, still quite easy to read.  But now we've started to complicate things a bit, and certain downstream tasks get harder.  Let's say I wanted to sort this list by last name.  That's going to be a challenge, since each full name is a single string.  I would need a way to identify the last name within the string.  I could potentially carve each string into pieces, and take the second piece.  But that would give me "H." for Mr. Macy.  Ok, so I need to take the last piece instead.  That should work as long as I haven't inadvertently entered "Pitt, Brad" somewhere. 

 

Ideally we need to split this data up in a known fashion, probably at the time of creation.  Relying on algorithms to extract information can be risky if the algorithm is wrong, so it is better to simply define things from the get go.  In this case, that means keeping the first name separate from the last name.  The question then becomes how to do that.

 

Don't Be A Dictionary

 

Level 2 in the programming learning curve leads to dictionaries.  At first glance, dictionaries are great.  They can contain anything, there is no hard structure, and in basic cases they can save data directly to plists without much trouble.  I'll use a mutable dictionary here since the code is easier to understand, but a sample actor represented in a dictionary might look like this:

 

NSMutableDictionary *actor = [NSMutableDictionary dictionary];
[actor setObject:@"Brad" forKey:@"firstName"];
[actor setObject:@"" forKey:@"middleName"];
[actor setObject:@"Pitt" forKey:@"lastName"];

 

The string has been broken up into pieces, and can now be located via an identifier, the key.  Retrieving a value is relatively simple:

 

NSString *actorFirstName = [actor objectForKey:@"firstName"];

 

Now instead of putting strings into an array, add dictionaries instead:

 

NSArray *actors = [NSArray arrayWithObjects:actor1, actor2, actor3, nil];

 

There are ways of sorting arrays using these keys, so it would seem that we've solved our problem.  For now, at any rate.

 

The problems with using dictionaries take a while to reveal themselves.  Let's say that you set aside this project for a couple of months to work on something else, and then come back to it in order to make changes.  You find yourself looking at this line of code:

 

NSDictionary *actor = [actors objectAtIndex:2];

 

Ok, so you're referencing a single dictionary in an array, and you're calling it an actor.  Great.  Now then, what should "actor" have in it?  Hrm, well, I sort of remember caring about the names, so probably names.  Maybe their birthdays.  Oh, it would be nice if there was a list of their movies too.  I probably thought that was a good idea when I made this, so I'll just assume this data is there.  Wonder what I called it.  Probably "birthday".  Great, let's see what happens:

 

NSString *birthday = [actor objectForKey:@"birthday"];
NSLog(@"birthday is: %@", birthday);

Log output: birthday is: (null)

 

Uh oh.  It's not there.  I must have called it something else.  And come to think of it, would it really be an NSString?  It's a date, and we have NSDate objects.  Might have been one of those.  How can I find out?  Well, I could log the entire dictionary:

 

NSLog(@"actor is: %@", [actor description])

 

Hrm, no birthday.  Is that because I didn't have information for this actor, or is that because none of the actors have birthdays?  Not sure.  And there isn't a handy way to find out, either.  All I can do is figure out where I first created these dictionaries, and see what is there.

 

The point that I'm making here in a roundabout way is that dictionaries do not document themselves.  Every NSDictionary looks the same from the outside.  It is the contents that matter, and short of manual interrogation, there is no easy way to find out what the contents of a given dictionary are or should be.  If you wanted to be diligent, you could put something in the comments somewhere like this:

 

// I hereby define an "actor" dictionary as containing the following data:
// First name shall be an NSString stored with the key "firstName"
// Middle name shall be an NSString stored with the key "middleName"
// Last name shall be an NSString stored with the key "lastName"

 

You could, but no one takes the time to do this.  And because dictionaries are so handy and flexible, you can add and remove keys all day long, and totally forget to update this list, at which point the list just becomes another point of confusion, rather than an aid.  Without this list, you do not have an easy way to figure out what this dictionary should contain.  You will have to waste time researching your own code and doing logging just to figure out what this should be.

 

But wait, there's more.  Let's say a common activity in our actor program is to display the full name of the actor.  That might look like this:

 

NSString *firstName = [actor objectForKey:@"firstName"];
NSString *lastName = [actor objectForKey:@"lastName"];
NSString *fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];

 

Blech.  That's a lot of code, copy-pasted all over the place.  And I forgot the middle initial.  And sometimes it outputs "Bob (null)".  So I need to make it more complicated, add some error handling, and then re-copy-paste it all over the place.  Ah ha! I know a little about OOP, I'll just make a helper method somewhere.  The only thing I know how to grab from anywhere is the app delegate, so I'll put it there!

 

- (NSString *)fullNameFromActor:(NSDictionary *)actor;

 

Brilliant!  Well, not so much.  I mean, this will of course work.  But look how far separated it is from your data.  All of these actors are defined way over in some view controller (or better yet, a data controller) somewhere, but this method is here in the app delegate.  Any time that you have one of these dictionaries in your hand, you have to go all the way over to the app delegate to get this method.  More advanced programmers might be tempted to move this code into a Category, and that's certainly an option, but a bit beyond the scope of this article and not a good solution for a case like this anyway.

 

The ideal solution to this problem would include some handy form of documentation that can be easily referenced later, and would also provide a convenient place to store helper methods.  And that ideal solution really isn't that hard to do.

 

Subclassy

 

Creating a custom model subclass is straightforward.  Create a new file, starting with Objective-C class, then choose to subclass NSObject.  By convention, class names should begin with a capital letter, so "Actor", not "actor".  For bonus points in avoiding name clashes, use some kind of prefix.  I use BTI, so I would name it BTIActor.  Do NOT use NS.  Do NOT use UI.  These are Apple's territories.  So NSActor and UIActor are wrong.  Period.  Wrong.  Don't do it.  No exceptions.

 

So we have a new class now:

// BTIActor.h
@interface BTIActor : NSObject
{

}
@end

 

…what do we do with it.  Well, with the dictionary, we were thinking in terms of keys.  Here in a model class, you think in terms of properties instead.  This is no different than defining a label property in a view controller.  Let's begin:

 

// BTIActor.h
@interface BTIActor : NSObject
{
}
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *middleName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, copy) NSDate *birthday; 
@end

 

Immediately we have a much, much better situation than we had with the dictionaries.  First of all, there is a list of each element in this model.  3 strings and a date.  No keys to remember or mistype, it's all right here.  And also notice that we know what everything is.  Ah, the birthday is a date object.  Nothing to remember, nothing to get wrong.  With dictionaries, it is always objectForKey:.  What kind of object?  I don't remember.  Have to go look it up, uh, somewhere.  With this class, all I have to do is click on the .h file anytime I want to know something.  It's easy for me to find today, and it's easy for me to find in 3 months.  It is inherently self-documenting.

 

And I'm not even done yet.  How about that helper method?  Put it here, too:

 

// BTIActor.h
@interface BTIActor : NSObject
{
}
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *middleName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, copy) NSDate *birthday;
- (NSString *)fullName;
@end

 

The data remains broken up so that you can still get to individual elements (last name) if you need to, but you can make as many handy methods as you need all right here on the object that can use them.  Want to do last name, first name?

- (NSString *)reversedFullName;

 

How old is this actor right now?

- (NSInteger)currentAge;

 

On and on and on.  If you have a need, make a method for it here, then you can use it wherever you use this model.  Granted, the implementations of these methods may not necessarily be any simpler this way, but at least you have everything you need right here.

 

The .m file wouldn't be all that different from an average view controller.  Synthesize your properties and implement your methods:

 

// BTIActor.m
#import "BTIActor.h"
@implementation BTIActor
@synthesize firstName = ivFirstName;
@synthesize lastName = ivLastName;
...
- (NSString *)fullName
{
   return [NSString stringWithFormat:@"%@ %@", [self firstName], [self lastName]];

... 
@end

 

There are a variety of other things to do here.  There should be a dealloc method (unless using ARC).  Might want an init method, too.  But chances are, this is all stuff you've done elsewhere before, most likely in a view controller.  Same idea here.

 

Model objects are easy to create, so there isn't much of a reason to hesitate to use them.  Describing a car?

// BTICar.h
@interface BTICar : NSObject
{
}
@property (nonatomic, copy) NSString *make;
@property (nonatomic, copy) NSString *model;
@property (nonatomic, assign) NSInteger *year;
@property (nonatomic, assign) NSInteger *numberOfDoors;
@property (nonatomic, assign) CGFloat engineDisplacement;
@end

 

Describing a shirt?

// BTIShirt.h
@interface BTIShirt : NSObject
{
}
@property (nonatomic, copy) NSString *size; // S, M, L, XL
@property (nonatomic, retain) UIColor *color;
@property (nonatomic, assign) BOOL isLongSleeve;
@property (nonatomic, assign) BOOL hasPoppedCollar;
@end

 

If you can figure out how to put this stuff into a dictionary, then you can do the same thing even better with a model class.  There really is nothing to be afraid of here.  So kick a dictionary to the curb today!

 

Work Your Model

 

How do you use a model class?  Well, the first thing you need to do is add this to the .m file wherever you want to use them:

#import "MySuperAwesomeModel.h"

 

Now you can use it.  Let's revisit the dictionary version:

NSMutableDictionary *actor = [NSMutableDictionary dictionary];
[actor setObject:@"Brad" forKey:@"firstName"];
[actor setObject:@"" forKey:@"middleName"];
[actor setObject:@"Pitt" forKey:@"lastName"];

 

Doing this same thing with the model object:

BTIActor *actor = [[BTIActor alloc] init];
[actor setFirstName:@"Brad"];
[actor setMiddleName:@""];
[actor setLastName:@"Pitt"]; 

 

It's cleaner, easier to read, and as a double-bonus, Xcode will help you type this stuff.  Xcode does not help at all when typing @"strings".  So you are much more likely to make a typo.  If you make a typo with dictionary keys, you won't figure it out until much letter.  Xcode will not warn you.  The key will simply not be what you expect it to be.  That's hard to troubleshoot.

 

Use them in arrays just like you did with dictionaries:

[actors addObject:actor];
...
BTIActor *someActor = [actors objectAtIndex:1];

 

Oh yeah, and don't forget the helper method:

NSString *fullName = [someActor fullName];
[[self nameLabel] setText:fullName]; 

That's more or less all there is to it.

 

There are a couple of other aspects to model objects that I will address with a future post, particularly related to saving and loading these objects.  If you are in a hurry to do so, do some Googling for "NSCoding".