It looks like you're new here. If you want to get involved, click one of these buttons!
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.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.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.// 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.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.
Replies
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: …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: 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: 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? How old is this actor right now? 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: 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? Describing a shirt? 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: Now you can use it. Let's revisit the dictionary version: Doing this same thing with the model object: 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: Oh yeah, and don't forget the helper method: 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".
SlickShopper 2 | BTIConcepts on GitHub | Free NSLog utility | Free Getter Utility | Leave a PayPal donation.
Are you a newbie? Things you should read:
Definitive Guide To Properties | UITableView Series | A Model (Object) Is A Beautiful Thing
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeSlickShopper 2 | BTIConcepts on GitHub | Free NSLog utility | Free Getter Utility | Leave a PayPal donation.
Are you a newbie? Things you should read:
Definitive Guide To Properties | UITableView Series | A Model (Object) Is A Beautiful Thing
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeSlickShopper 2 | BTIConcepts on GitHub | Free NSLog utility | Free Getter Utility | Leave a PayPal donation.
Are you a newbie? Things you should read:
Definitive Guide To Properties | UITableView Series | A Model (Object) Is A Beautiful Thing
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeI've definitly "kicked Dictionaries & Categories to the curb" after reading this.
You do also have a wonderful way, not tech dry, to spread your're knowledge with humor and good, easy to understand IRL cases.
Keep up the good work!
P.s. Did you write any more tutorials on objects "adding, reading & handle them" as you mentioned last in this tutorial? Would love to read them...
- Spam
- Abuse
- Troll
1 • Off Topic Insightful Disagree Dislike 1Like AwesomeJust wanted to say that I think it would help if you showed the readers how they can initialise a model without having to set every property manually, via an -init method:
Furthermore, instead of allowing the properties to be written as well as being read (in some cases) readonly properties can be used, with an NSDictionary being the only retained object.
Usage:
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeAlso, your example here isn't very useful based on your description. A method like this would be useful for converting the object to/from a dictionary. But from a "save effort while creating the object" standpoint, it really doesn't. You'd have to already have this dictionary in order for the method to save any effort, but then you've moved the effort to creating the dictionary.
NSCoding is generally a better approach anyway.
SlickShopper 2 | BTIConcepts on GitHub | Free NSLog utility | Free Getter Utility | Leave a PayPal donation.
Are you a newbie? Things you should read:
Definitive Guide To Properties | UITableView Series | A Model (Object) Is A Beautiful Thing
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomePlease see the update in the above post, I showed another approach to creating models.
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeI guess I don't see the point of keeping the dictionary around in that model. You're going through the effort of defining properties, but leaving them as read-only. So effectively, you have a read-only model. I suppose that could make sense in certain circumstances, but in general I don't see what is being gained. For all of the effort you are going through, you could make a custom setter (or just a method, not even a property) for the dictionary that extracts the necessary information and populates the properties. I suppose you might be speeding up the initial object creation, but then every single time you access a property after that, it would be slower because every single getter involves a dictionary lookup. Chances are that you will ping aspects of an object many, many more times than you would create it, so you've slowed down the stuff that is important.
And then this really pegs my don't-get-the-point-ometer:
The whole point of this article was to do away with this mess. Effectively all that you've done here is change "NSDictionary" to "MKDeveloperModel", which might be useful for debugging, but otherwise you haven't done anything to simplify life.
(not to mention that you should be #defining constants for your keys)
SlickShopper 2 | BTIConcepts on GitHub | Free NSLog utility | Free Getter Utility | Leave a PayPal donation.
Are you a newbie? Things you should read:
Definitive Guide To Properties | UITableView Series | A Model (Object) Is A Beautiful Thing
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like AwesomeAs for # defining the constants, I didn't think to put that in for the example, got spot though!
- Spam
- Abuse
- Troll
0 • Off Topic Insightful Disagree Dislike Like Awesome