So here is the question just asked of me: Is it better to have a single notification (name) sent out and have all interested parties check the user info to see if the notification contains information for them, or to have the differentiator encoded in the notification name, thus having potentially hundreds or thousands of “types” of notifications sent out, but have only one listener get the message?

Well, in the simplest cases, when there are only a few messages, and only a few listeners, and few objects that wake up to look at the userInfo to see if it is interesting, one might opt for the simpler programming / inheritance model and have very little code, reused via inheritance that does all the work. E.G. all objects inheriting from BaseObject listen for message “foo” and then inherit the code for:

-(void)fooNotification:(NSNotification *)notif {
  if ([self.id isEqual:[notif.userInfo objectForKey:@"id"]]) {
    [self handleFoo:userInfo]; // do something
  }
}

The BaseObject class has the id filter notification handler (above), a no-op handleFoo:, which sub-classes can override, and the sender always sends out the same “foo” notification, with the differentiator tucked away in the userInfo for the recipient.

Sounds good, no? Lots of inheritance, code reuse, simplicity. The “leaf-node” classes just implement handleFoo: if they need to. All works great. But where does this break down?

Say you had a thousand BaseObject subclass instances. When they are created, they ask a Loader for some resource and listen for loaderDone Notifications. Loader finishes each request, sends a notification, moves on to the next. (You are using an NSOperationsQueue to manage your Loader pool, right?) Instances get pinged after operations complete and one says “Hey! That’s me!” and does something significant, like update a bit of UI, while the others pass. The rub? One thousand objects wake up and run a few lines of code for each one that does anything. Code is simple, easy to test. Low message dispatch cost. Probably insignificant overhead on your spiffy new PowerMac. Zero problem in testing with a few objects. But on the iPhone / iPad this is 9,999 objects too many involved in handling the event.

What do you do?

First is just good practice: if you don’t care, don’t listen. Don’t register to observe a notification until you are sure you care, then if you no longer need to listen, unregister. For our pseudo-code, only listen between “I need a foo” and “I got a foo” – outside that take yourself out of the loop. That can only help.

The real trick, when you see this pattern, is to find a way from our default case above, where response time depends on number of potentially interested objects, to a response time dependent on just number of actual interested objects. In the base case, where we have N potentially interested objects and just 1 interested object, we are going from a fluctuating case, based on set size N, to a stable linear case. This in turn leads to more predictable and scale-free (or close to it) response times.

(In our initial example above, response time is equal to “([number of objects] * ([filter time per object] + [message time per object])) + ([number of interested objects] * [handle time per object])”. What we want is to only message the interested objects, so we can be “[number of interested objects] * ([message time per object] + [handle time per object])”.)

So what is the simple trick to do this? Have the listening objects listen for a NSNotification with a name like:

  NSString *notifName = [NSString stringWithFormat:@"foo-%@-notification", self.id];

and have the Loaders post matching notifications. The observer lookup table will be a bit larger, with more messages registered, but the observer count will be the same. The lookup will mostly be a hashed lookup based on the memory location of the notification name’s unique string, and on ObjC / Foundation, that kind of lookup is FAST for the set size we are looking at. And even for large sets, we are so much better off paying the price to look up a single observer, than to have to run 4 lines of code in a very large number of objects.

Things that can make the bad case worse?

  1. Side-effect cost: You never know what the cost is of asking an object you haven’t written yet a simple question. (“My id? Sure, that’s stored in my NIB file. Which needs to be (re)loaded sometimes. Which instantiates other objects. Which do stuff.”)
  2. Threads: An even worse cost is the per-object code that does something that launches or spans thread boundaries. (“My id? Let me fetch that in a new thread. Or in the main thread, which could block stuff.”) Opportunity for thread overload or lock-ups with contention for resources.

Linear response is your friend. Do a little extra work. Easier to debug. Easier to optimize. Happier users.

Remember – your iPhone is like a 100mhz Pentium. CPU cycles are your most precious resource. And less executing code in less objects usually means less less memory, which is our second most precious resource. Less work, less memory, app runs faster, users happier. Everyone wins.

NOTE: For everyone going to WWDC this year, CodeFab will be hosting several iPad/iPhone related events at the SF iPhone Mansion again. On the Saturday and Sunday before WWDC we will be hosting a Mini iPad/iPhone DevCamp (Register Here) in conjunction with our LA iPhone DevCamp/Developer Meetup partners. We will also be hosting a fabulous iPhone Party on (tentative) Tuesday Night. (Hope you remember last year’s blast!)

Tagged with:  

Register now and get Early Bird Discount pricing!

April 24-25 – LA iPhone/iPad Developer Special Ops Advanced Class
Register here: http://laipadtraining.eventbrite.com/

May 8-9 – NYC iPhone/iPad Developer Special Ops Advanced Class
Register here: http://nycipadtraining.eventbrite.com/

May 22-23 – Chicago iPhone/iPad Developer Special Ops Advanced Class
Register here: http://chicagoipadtraining.eventbrite.com/

Tagged with: