Using blocks as a flexible callback mechanism

September 13, 2009

Among the many numerous developer improvements in Snow Leopard is the addition of blocks to the Objective-C language (now at version 2.1). While working on some networking code using NSURLConnection I discovered that blocks provide a beautiful and flexible mechanism for implementing callbacks.

As a quick example, let’s say you want to initiate two GET requests to different websites. Perhaps the first is to post some form data and the other is just to download the website content. No matter the particular specifics, the idea is that for different types of NSURLConnection requests you will want to respond differently upon completion. For the first you might want to update the UI to show that the form data was posted. For the second you may want to display the site in a web view. To accomplish this you would traditionally need to keep an array around to hold not only the NSURLConnection instances themselves, but some kind of associated identifier for each one. Otherwise, when your delegate methods fire you will have no idea which action initiated the request in the first place – and thusly you wouldn’t necessarily know what to do next after the connection is finished loading.

The following is a bit of untested pseudo-code (a rough approximation if you will) of one of the traditional methods of handling this:

 
- (void)performMyFirstRequest
{
  NSURLRequest *request = [NSURLRequest requestWithURL:myFirstURL];
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                         delegate:self];
 
  [_connections addObject:[NSDictionary dictionaryWithObject:connection, 
       @"NSURLConnection", @"myFirstRequestIdentifier", @"identifier", nil]];
}
 
- (void)performMySecondRequest
{
  NSURLRequest *request = [NSURLRequest requestWithURL:mySecondURL];
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                         delegate:self];	
  [_connections addObject:[NSDictionary dictionaryWithObject:connection, 
       @"NSURLConnection", @"someOtherIdentifierHere", @"identifier", nil]];
}
 
- (void)connection:(NSURLConnection *)connection 
           didReceiveResponse:(NSURLResponse *)response
{
  NSDictionary *connectionInfo = [self _infoForConnection:connection];
  NSString *identifier = [connectionInfo objectForKey:@"identifier"];
 
  if ([identifier isEqualToString:@"myFirstRequestIdentifier"])
    NSLog( @"The first request was successful!" ); // do something else
  else if ([identifier isEqualToString:@"someOtherIdentifierHere"])
    NSLog( @"The second request was successful!" );
 
  // Remember to remove the connectionInfo dictionary from our _connections 
  // ivar once the connection is finished loading
}
 
- (NSDictionary *)_infoForConnection:(NSURLConnection *)connection
{
  for (NSDictionary *connectionInfo in _connections)
  {
    if ([connectionInfo objectForKey:@"NSURLConnection"] == connection)
      return connectionInfo;
  }
 
  return nil;
}

In short, you will have to have a bunch of conditional if-else statements in order to properly respond to each different connection’s completion. The alternative approach is to write a URLConnectionManager class based on blocks:

 
// URLConnectionManager.h
 
@interface URLConnectionManager : NSObject 
 
- (void)performRequest:(NSURLRequest *)request 
         responseBlock:(void (^)(NSURLResponse *response))responseBlock;
 
@end
 
// URLConnectionManager.m
 
- (void)performRequest:(NSURLRequest *)request 
         responseBlock:(void (^)(NSURLResponse *response))responseBlock
{
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                          delegate:self];
  [connection associateValue:(id)responseBlock withKey:@"responseBlock"];
}
 
- (void)connection:(NSURLConnection *)connection 
           didReceiveResponse:(NSURLResponse *)response
{
  void (^responseBlock)(NSURLResponse *) = [connection associatedValueForKey:
                                                   @"responseBlock"];
  responseBlock( response );
}

This code also takes advantage of associated objects, another new language feature that allows us to avoid having to keep an array of connection info dictionaries around to store the associated responseBlock reference. (I am using an elegant category wrapper of the C API written by Andy Matuschak.)

Using this approach, the two connection requests can be implemented as follows:

 
// The first request
NSURLRequest *request = [NSURLRequest requestWithURL:myFirstURL];
 
[connectionManager performRequest:request 
                    responseBlock:^(NSURLResponse *response) {
  NSLog( @"Received a response from myFirstURL: %ld %@", 
                [(NSHTTPURLResponse *)response statusCode], response );
  // Perform whatever other specific magic you need for this response!
}];
 
// The second request
NSURLRequest *request = [NSURLRequest requestWithURL:mySecondURL];
 
[connectionManager performRequest:request 
                    responseBlock:^(NSURLResponse *response) {
  NSLog( @"Received a response from mySecondURL: %ld %@",
                [(NSHTTPURLResponse *)response statusCode], response );
  // Perform whatever other specific magic you need for this response!
}];

Note that there are no more if-else statements to deal with and even though the requests are being performed asynchronously, you can write a piece of code to deal with the response in the same spot that you make the initial connection invocation. This results in a much cleaner and more readable codebase.

Browse and/or download the full Xcode sample project on GitHub

Why support matters

April 8, 2009

I’ve been a happy paid user of CSSEdit 2 for well over two years now. So when MacRabbit released Espresso it seemed only logical for me to try it out. I used it for about a week until I deemed it fit for purchase. I sauntered on over to the MacRabbit online store and discovered (as pretty much expected) that there was a discount purchase option for current CSSEdit 2 owners (at 49.95€ rather than the full 59.95€). To qualify one has to enter a valid CSSEdit 2 license code, so I dutifully looked mine up in my e-mail archives, copied it, and pasted it into the appropriate text field.

But instead of allowing me to complete the order I was greeted by a message stating that my license code “is not a valid CSSEdit 2 code.” Of course, I knew this claim to be false since this is the same code that works just fine in my still functioning copy of CSSEdit. Just to be sure though I headed on over to the MacRabbit support page where I hoped the license code lookup form would offer some kind of resolution. I entered my e-mail address (which I double-checked in my mail archives to be the one I originally used to register CSSEdit) to have my code sent to me. That didn’t quite work:

“Couldn’t find anything with this e-mail address! Are you sure it is correct?”

The next step was to shoot off an e-mail to the support people:

Subject: Espresso discount for CSSEdit 2 owners
Date: March 24, 2009 22:43:36 EDT

Hi,

I am trying to purchase Espresso via the CSSEdit 2 discount option. The form, however, is not accepting my license code:

Name: (redacted)
Code: (redacted)

It claims this is not a valid CSSEdit 2 license code. Furthermore, I don’t appear to be in your lost code lookup database either.

Thanks.

Indeed, I sent this on March 24th. Somewhere around three weeks later I was left wondering if MacRabbit even has any support people. But just to be sure, I sent a follow-up on April 6th.

Subject: Re: Espresso discount for CSSEdit 2 owners
Date: April 6, 2009 18:17:59 EDT

A reply would be most appreciated as there are only 2 more days left in the trial period.

Since I have fit Espresso nicely into my workflow for several projects and its trial period is not of infinite duration, I had to make a choice. Either I could buy it at the full price of 59.95€ and be done with it, or I could partake of the MacHeist 3 bundle to get a license for considerably less than even the discount option I was initially trying to use. I wasn’t originally looking to get the MacHeist bundle since I would rather just directly pay the authors of the one application I actually need. Quality software development is hard work and developing a good application can take an inordinate amount of time, so I don’t mind rewarding such effort by paying a fair price.

Alas, quality software is a package deal. If I am going to pay a quality price I expect a certain basic level of support. It is rather difficult to justify paying full price for a piece of software when the support part of it is not being handled very well (or, seemingly, not being handled at all). Therefore, I went with the MacHeist bundle. The outcome is quite favorable for me as I can continue using Espresso to get work done. I can’t say it is nearly as favorable for MacRabbit’s cashflow.

It is certainly not my intent to bludgeon MacRabbit with this. They make fine software. But they do need to tighten up ship a bit, for their own sake.

Switching to Google Reader

March 31, 2009

For several years now I have been using Cyndicate as my RSS feed reader. It is an excellent piece of software, but I have come to realize that I really don’t use any of the features that make the application so unique among its competition. I don’t use any filters, I don’t use ratings, and though I was taking advantage of its persistent architecture by filing interesting articles into various folders for future reference, it is a very rare occasion that I ever go into these archives to re-read anything I put there.

The most important thing I have been wanting for some time is something Cyndicate doesn’t offer right now. And that is the ability to synchronize across multiple devices. In particular I want to be able to check my feeds on my iPhone or MacBook Pro when I am away from my desktop machine. Sometimes I’d like to skim or even read read posts while standing in line.

In short, I have decided to switch to Google Reader for all of my subscriptions. Not content with using the web interface, however, I am trying out EventBox on the Mac side of this; for the iPhone it is Byline. Neither of these applications is really a terribly impressive fit and I will definitely miss Cyndicate’s persistence and overall polish. But for now this looks like a trade-off I am willing to deal with.

The trouble with Adobe

September 21, 2008

I can’t remember a time when any one of Adobe’s Mac applications had an interface that felt like it truly belonged on my Mac. I don’t recall the problems being as pronounced in the pre-Mac OS X days, but there were always—at best—annoying quirks to be found. At worst, using their software made me feel like punching my computer screen.

But then Adobe brought their suite of applications to Mac OS X, and things got even worse. And ever since, they’ve spent each successive release screwing up their user experience with remarkable consistency. It’s to the point that the thought of punching my screen isn’t even enough. I want to reach into my Mac and beat the living snot out of every piece of the Adobe Creative Suite 3 I can find.

And, predictably, if the Fireworks CS4 beta is an indicator of what’s going to happen to the rest of the suite, Adobe obviously still doesn’t get it. Sadly, if a company has spent this many decades in the business and continues to utterly fail in this respect, it may be safe to bet they never will get it either.

Because when it comes down to it, I don’t need a window whose title bar is lacking a title and has instead been cluttered with a bunch of toolbar icons and a “mode” pop-up menu. In fact, I don’t even want a bunch of confusing interface “modes” to choose from. I don’t particularly care about throwing the palettes and everything else into one gigantic tabbed window. All of that is nothing more than extraneous fluff that’s detracting attention and resources away from what really needs to be fixed.

It is especially ironic that John Nack cites Panic’s Coda as an example of a single window application, because Panic would never ship something this utterly tasteless (not even as an internal beta, much less a public one). If you’re looking to Panic for inspiration, emulate their attention to detail. The suite’s problem isn’t that it needs a revolutionary new interface concept. Quite the opposite, in fact. The focus needs to be on the little things, because they all add up to one big mess of inconsistency and annoyance.

Perhaps Adobe needs to acquire some semblance of taste before worrying about tabbed windows and monkeying with title bars.