Showing posts with label subclass. Show all posts
Showing posts with label subclass. Show all posts

Wednesday, May 7, 2014

Bringing It All Together: Notifications

There are some routine activities in iOS development that involve spreading related code across multiple methods. In my early days, I just took this for granted as The Way Things Are Done. As my skills and experience grew, some of these aspects started to rub me the wrong way, and eventually I became irritated enough to come up with my own solutions. So over the next couple posts, I'd like to describe those solutions and explain how they came to be.

 

The first topic is notifications, specifically setting up a UIViewController subclass to listen for them. I was initially set along the path of "something needs to change" by this post. Here the author argues against the use of what I call "Nuclear Unregister" in viewDidDisappear:, like so:

 

- (void)viewDidDisappear:(BOOL)animated
{
   [super viewDidDisappear:animated];

   [[NSNotificationCenter defaultCenter] removeObserver:self];
}

 

The author states - and I wholeheartedly agree - that this should only be done in dealloc. He describes how doing this in a UIViewController subclass also winds up unsubscribing from any notifications that UIViewController would be otherwise listening for, such as memory warnings. I think Apple has made some internal changes since this was written 3 years ago, as I was indeed still able to get memory warning notifications after doing this in iOS 7, but the larger point remains. Maybe the superclass is depending on some other notifications that are still in play when the view is off screen. Maybe future subclasses will, too. Going with the nuclear option means you are turning off listeners that you are not responsible for and possibly have no control over, and this happens at a point when the object in question is very much still alive and will likely be used again in the future. At minimum it is lazy, but on the other end of the spectrum you are stepping on toes that shouldn't be stepped on. Sure, your listeners will be reactivated in viewWillAppear, but if sub/superclasses had them defined in -init, they won't be coming back on again.

 

Whether or not you agree that this is an issue - and by this point, I certainly hope that you do - the argument was convincing enough for me to revisit lots of code where I had been doing it wrong. And I then moved forward with a strict policy of only unregistering the specific notifications that I had originally registered.

 

This means that NSNotificationCenter code gets spread out to numerous locations. The vast majority of them will be added in viewWill/DidAppear:

 

- (void)viewDidAppear:(BOOL)animated
{
   [super viewDidAppear:animated];

   NSNotificationCenter *notificationCenter = ...;
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
   [notificationCenter addObserver: selector: name: object: ];
}

 

And then you have to remember to turn them off in viewWill/DidDisappear:

 

- (void)viewDidDisappear:(BOOL)animated
{
   [super viewDidDisappear:animated];

   NSNotificationCenter *notificationCenter = ...;
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
   [notificationCenter removeObserver: name: object: ];
}

 

And this is just for the ones that need to be active only when on screen. If you need more that will be active for the life of this object, then you have to handle init. But if this view controller is created in a XIB file, it won't run through init, so you have to cover -awakeFromNib too. And then of course you do the nuclear unregister in dealloc. So that is potentially 5 different methods where some code will be placed. This is spreading things out too much for me.

 

Worse, there is nothing inherently linking any of these locations. Say you change your mind, and one of these viewDidAppear ones needs to go to init instead. Now you have to remember to remove the corresponding line from viewDidDisappear, because if you don't it could potentially be shut off early. Are you going to remember to do that every time? Will the new person on the project even know that they should? It is related code in multiple methods that contains no additional documentation about what the intent should be.

 

Also it's annoying to have to type out the removeObserver: lines again. Even if you copy-paste from the addObserver: lines, you still have to edit each one. The 3 parameters in removeObserver: are exactly the same as 3-out-of-4 of the addObserver: parameters, but you don't get any code-completion help typing out the combination. It's a situation just begging for mistakes.

 

That pretty much covers all of my pain points with notifications, so let's move on to my attempt to address them. I was not able to come up with a single class or technique that solves all of these issues. They are similar-but-different method calls that have to happen. They will need to be called at different times. It's a tough nut to crack, but I came up with a system that I feel does a really good job of making this process as simple as it can be. The classes involved are part of my BTIKit project, so you can just hop over there and look at code if you don't care to read any more.

 

Step 1 is basically to refactor the addObserver/removeObserver methods. If it can be broken down to as simple as "startListening" and "stopListening" that would be great. Naturally these methods do not contain enough information, as we're dealing with 3 or 4 parameters ultimately, so I need an object that can store the appropriate values until they are needed. Then they can be used by these simpler methods.

 

Enter BTINotificationInfo.

 

This class has properties for storing the observer, the selector, the name, and the object. But those are all private as they shouldn't be messed with after this object is created, so let's talk just about the initializer:

 

+ (instancetype)infoWithObserver:(id)observer
   selector:(SEL)selector
   name:(NSString *)name
   object:(id)object
   lifespan:(BTINotificationInfoLifespan)lifespan;

 

One of these objects is created with the exact same parameters that would be involved with addObserver. There is an additional lifespan value that I'll discuss in a moment. Then we do indeed have our simpler methods:

 

- (void)startListening;
- (void)stopListening;

 

The initializer places the parameters into the appropriate properties, and then these methods reference them as needed to do the addObserver/removeObserver stuff. Right off the bat, one potential source of error has been removed: the largely-duplicated parameters in removeObserver. Instead of appearing twice (add + remove), they appear once. The values are defined in one place. Then using this object, you can start/stop listening as much as you want, repeatedly, without having to type those values again.

 

Of course, add/remove observer are called from different methods typically, so you will want to keep this object around somewhere. As I said, I was not able to come up with a single-class solution. But this does serve as the building block for the entire system. And this object can be used standalone. An example would be in Core Data, where you might start listening for a managed object context save, do the save, and then stop listening. Rather than type out the parameters twice, just create one of these objects. startListening before, save, stopListening when finished. Easy.

 

I mentioned the additional lifespan parameter. With view controllers, the vast majority of notification needs fall into 2 categories: 1) Listen only when the view is on screen, or 2) Listen the entire time this view controller is alive. If there have been other situations, they didn't sink into my mind enough to care about. And then I haven't really had too many consistent notification needs outside of view controllers that follow any particular pattern. So the rest of the solution is geared towards view controllers, and this lifespan parameter is how you define which category you fall into. The value is an enum, with pre-defined values of "lifetime", "visible", and "custom". While defining all of the notification listener stuff, you can also define the period during which this listener should be active.

 

This addresses another one of my pain points. Instead of relying on the addObserver code to be in a particular method to explain the intent, now you explicitly define it. And it is easy to change your mind. The case I mentioned previously about moving the addObserver from viewDidAppear to init goes away. Change the lifespan value, done. Nothing else needs to change.

The BTINotificationInfo object doesn't use the lifespan value itself. Actually this is the only property that is allowed to be public (read-only), so that other classes can make use of it. For the vast majority of my needs, "other classes" have been view controllers. And a view controller makes up the other half of this solution: BTIViewController.

 

The first thing that BTIViewController provides is a single place to define ALL (or at least "most", certainly) of your BTINotificationInfo objects:

 

- (void)populateNotificationInfos NS_REQUIRES_SUPER;

 

This is subclass-friendly, so any view controllers subclassing this can define their own notification infos. This method will be automatically called when the view controller is created.

 

BTINotificationInfo has that lifespan property, so you can define all of your "visible" and "lifetime" notifications right along side each other, in the same method. Since everything is defined in one place, that reduces the amount of effort required to investigate all of your notification listener setup.

 

Two simple methods make the magic happen:

 

- (void)startListeningForNotificationsWithLifespan:(BTINotificationInfoLifespan)lifespan;
- (void)stopListeningForNotificationsWithLifespan:(BTINotificationInfoLifespan)lifespan;

 

These can be called manually if desired, but there probably isn't much of a reason to do so. BTIViewController is already set up to register the "lifetime" ones in init/awakeFromNib, and handle the "visible" ones in viewWill(Dis)Appear. Pretty much the only reason to call these methods manually would be to address any "custom" ones, but I will make a guess that it won't make much sense to handle special-case ones along with the others. I could be wrong.

 

BTIViewController has a primary method for adding notification infos:

 

- (void)addNotificationInfo:(BTINotificationInfo *)info;

 

...and then a whole bunch of shorthand helper methods that reduce the amount of code required even further. Let's look at a typical example:

 

[[NSNotificationCenter defaultCenter] addObserver:self
   selector:@selector(keyboardWillShow:)
   name:UIKeyboardWillShowNotification
   object:nil];

 

To do this same thing with the generic methods would be:

 

BTINotificationInfo *notificationInfo = [BTINotificationInfo infoWithObserver:self
   selector:@selector(keyboardWillShow:)
   name:UIKeyboardWillShowNotification
   object:nil
   lifespan:BTINotificationInfoLifespanVisible];
[self addNotificationInfo:notificationInfo];

 

Ok, so it's a bit wordier. Here come the shorthand methods to save the day:

 

[self addVisibleNotificationInfoForName:UIKeyboardWillShowNotification
   selector:@selector(keyboardWillShow:)
   object:nil];

 

Boom, less code. The shorthand methods assume that the view controller - self - will be the observer. And the lifespan is incorporated in the name of the method.

 

One thing I haven't talked about yet is block-based notification handlers. For one thing, I never considered separate methods for notification responders to be a big deal, so they don't really solve a significant problem for me. For another, you have to keep track of the goofy observer object in order to removeObserver. So between solving a problem I didn't think I had and actually creating some work I didn't otherwise need to do, I didn't give them much thought.

 

Then I realized that with BTINotificationInfo I'm already keeping around an object for the purpose of unregistering. Since I'm doing that, that object can keep track of the observer object, too. And suddenly block-based notification handlers got more interesting, so I decided to support them and also included shorthand methods in BTIViewController. Like so:

 

[self addVisibleNotificationInfoForName:UIKeyboardWillShowNotification
   object:nil
   usingBlock:^(NSNotification *notification) {
      // Resize/reposition some views
   }];

 

We've pretty much got the holy grail here. Everything, and I mean everything, defined in one place.

 

So the steps that you need to take in order to experience this awesomeness are:

  • Acquire BTIKit.
  • Make your view controllers inherit from BTIViewController.
  • Override -populateNotificationInfos. Call super first.
  • Define the notifications that you care about.

 

That's it. BTIViewController will do the rest for you. No more typing essentially duplicated notification code in multiple methods. No more scrolling back and forth in your implementation file trying to find which notifications this class cares about. BTIViewController even does the nuclear unregister in dealloc, so you don't have to include it yourself anymore.

 

Enjoy.

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".