Notebook

While developing our first iOS application pinPics, we were stymied by a high-level API problem. The solution required digging into the deepest internals of the system. We survived the ordeal and in doing so, released our first open source Objective-C library.

Protocols

Many modern object-oriented programming languages provide a mechanism for modeling the cross-cutting concerns in a class hierarchy. The canonical example of this is Java's interfaces, but many object systems have them. Perl code can be factored into roles, you can build protocols in Objective-C, and so on. These constructs are all similar in that each of them contains a list of related method names. Classes can conform to a protocol by implementing all of the methods required by that protocol. Then you can enforce that an object passed as a parameter or property must conform to a particular protocol. This type-checking ensures that the object provides the API you expect. It's similar to the polymorphism that inheritance offers, but a class can conform to many different protocols, and is not bound by the constraints that superclasses would impose.

Historically, Objective-C was the first language with this kind of feature built into its object-orientation system. Back in the NeXT days, the way to communicate between computers (or processes, or threads) was to publish your objects and allow for other systems to invoke methods on those objects. Your process would have a local proxy object for that real, remote object. Any method call on the local proxy would be sent across the wire, but that was abstracted away as an implementation detail1.

In practice, a lot of the overhead of this system was in simply asking objects about their capabilities. There might be a dozen respondsToSelector: calls before the real work could begin. Eventually the NeXT team developed a way to reduce those dozen respondsToSelector: method calls down to one. By packaging up those methods into a protocol, you could confirm that the remote object met your needs with a single call to method conformsToProtocol:. So, protocols were originally invented to reduce the overhead of the networked object system.

The idea spread from there, and nowadays we put protocols (and interfaces, and roles, and so on) to work for many different needs. Apple makes heavy use of protocols to manage the division of responsibility in many of its APIs. The UITableViewDataSource protocol is a great example of that. You implement a couple methods (tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath:) that describe the content of the table. Then, armed with that data source, Apple's own UITableView manages rendering the table view for you, including recycling cells for reuse to help reach 60 FPS for scrolling.

In the course of building iOS apps, I create dozens of custom protocols to manage the communication between disparate objects. Channeling all communication through protocols loosens the coupling between objects. Any other object that conforms to a given protocol can be substituted in, which improves both testability and maintainability.

One place where user-defined protocols are a particularly great fit is the view controller containment system which lets you embed a full-fledged view controller inside another. The internal view controller publishes a protocol that parent view controllers are expected to conform to. This guides the way that the internal view controller can communicate with the external view controller, while also making sure that they are not tightly coupled. Any other view controller could embed this child view controller just by implementing the interface it demands. And that interface is both documented and enforced at compile time.

Protocols Dun Goofed

That's all well and good, but there are places where this design is overwrought. The worst offender is UIAlertView. When you want to display a popup dialog box (e.g for an error notification or to get confirmation on a dangerous action) you can use UIAlertView. The way UIAlertView communicates back to you is through the UIAlertViewDelegate protocol. There is really only one interesting method in that protocol, which is alertView:clickedButtonAtIndex:2. This, of course, is the way that UIAlertView tells you which button was tapped. So here's how you might implement a delete confirmation using it.

-(void)didTapDelete {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"You sure 'bout that?", nil)
                                                    message:nil
                                                   delegate:self
                                          cancelButtonTitle:NSLocalizedString(@"Belay that order!", nil)
                                          otherButtonTitles:NSLocalizedString(@"Absofruitly", nil), nil];

    [alert show];
}

-(void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)index {
    if (index == [alertView firstOtherButtonIndex]) {
        [self.record delete];
    }
    // otherwise, cancel button. nothing to do, alert will be dismissed automatically
}

That's perfectly cromulent. But it does get to be a problem once you have the same view controller class producing and handling multiple instances of UIAlertView. In such cases, you must keep track of which type of alert you are dealing with, so that alertView:clickedButtonAtIndex: can piece together what to do. I originally dealt with this by adding an enum property on the view controller, which tracked all possible alert views. The code that produced the alert would set this property, and then alertView:clickedButtonAtIndex: could be one giant switch statement. To see what that looks like in code, let's add another alert for when sync fails, letting the user retry if desired.

-(void)didTapDelete {
    self.alertType = IIRecordViewControllerAlertDeleteConfirm;
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"You sure 'bout that?", nil)
                                                    message:nil
                                                   delegate:self
                                          cancelButtonTitle:NSLocalizedString(@"Belay that order!", nil)
                                          otherButtonTitles:NSLocalizedString(@"Absofruitly", nil), nil];
    [alert show];
}

-(void)syncEngine:(IISyncEngine *)sync didFailForRecord:(IIRecord *)record {
    self.alertType = IIRecordViewControllerAlertSyncFailed;
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Sorry about your syncing problem", nil)
                                                    message:nil
                                                   delegate:self
                                          cancelButtonTitle:NSLocalizedString(@"Fuhgeddaboutit", nil)
                                          otherButtonTitles:NSLocalizedString(@"Get back in there, tiger", nil), nil];
    [alert show];
}

-(void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)index {
    IIRecordViewControllerAlert type = self.alertType;
    self.alertType = IIRecordViewControllerAlertSentinel;

    switch (alertType) {

        case IIRecordViewControllerAlertDeleteConfirm:
            if (index == [alertView firstOtherButtonIndex]) {
                [self.record delete];
            }
            // otherwise, cancel button. nothing to do, alert will be dismissed automatically
            break;

        case IIRecordViewControllerAlertSyncFailed:
            if (index == [alertView firstOtherButtonIndex]) { // retry
                [self sync];
            }
            // otherwise, cancel button. nothing to do, alert will be dismissed automatically
            break;

        case IIRecordViewControllerAlertSentinel:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid alertType %d", alertType];

    }
}

As you can imagine, this gets unwieldy very quickly.

One useful pattern in Apple's own protocols is that they often include the relevant object as the first parameter in protocol methods. In many cases this means you don't have to store that object in a property. Here, we have access to the alertView object itself as the first parameter. So another solution might be to consult the alertView parameter's properties to guess which alert is being answered. You might do a string match on the title, but that strikes me as brittle, both for general maintenance (what if the title changes?), and especially for localization. Either way, there is seemingly no way of escaping the monster-sized alertView:clickedButtonAtIndex:, since that is the method that UIAlertView calls.

IIDelegate

I was sick and tired of managing alert views like this. Because there is so much friction in using UIAlertView, I sought other solutions whenever possible. This ended up being a benefit because modal dialogs are user hostile. That is merely a happy coincidence though, since this problem could have manifested with any protocol-based API. I wanted a better way in general.

iOS 4.0 introduced blocks (aka closures) into its Objective-C runtime. Coming from Perl I live and breathe closures, so it seemed obvious that a block-based API for UIAlertView would alleviate my complaints about it. However there was no reason to limit the solution to just UIAlertView. By implementing a more generic solution that works for any protocol, all instances of this problem are solved for everybody, rather than just the one.

I am happy to share with you IIDelegate, which turns any protocol-based API into a block-based API. Here is what the above example looks like using blocks.

-(void)didTapDelete {
    id<UIAlertViewDelegate> delegate = [IIDelegate delegateForProtocol:@protocol(UIAlertViewDelegate)
                                                   withMethods:@{

        @"alertView:clickedButtonAtIndex:":^(id _delegate, UIAlertView *alertView, NSInteger buttonIndex) {
            if (index == [alertView firstOtherButtonIndex]) {
                [self.record delete];
            }
            // otherwise, cancel button. nothing to do, alert will be dismissed automatically
        }
    }];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"You sure 'bout that?", nil)
                                                    message:nil
                                                   delegate:delegate
                                          cancelButtonTitle:NSLocalizedString(@"Belay that order!", nil)
                                          otherButtonTitles:NSLocalizedString(@"Absofruitly", nil), nil];
    [alert show];
}

-(void)syncEngine:(IISyncEngine *)sync didFailForRecord:(IIRecord *)record {
    id<UIAlertViewDelegate> delegate = [IIDelegate delegateForProtocol:@protocol(UIAlertViewDelegate)
                                                   withMethods:@{

        @"alertView:clickedButtonAtIndex:":^(id _delegate, UIAlertView *alertView, NSInteger buttonIndex) {
            if (index == [alertView firstOtherButtonIndex]) { // retry
                [self sync];
            }
            // otherwise, cancel button. nothing to do, alert will be dismissed automatically
        }
    }];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Sorry about your syncing problem", nil)
                                                    message:nil
                                                   delegate:delegate
                                          cancelButtonTitle:NSLocalizedString(@"Fuhgeddaboutit", nil)
                                          otherButtonTitles:NSLocalizedString(@"Get back in there, tiger", nil), nil];
    [alert show];
}

This implementation has a number of advantages. There is no gigantic alertView:clickedButtonAtIndex: that must dispatch based on alert type. That means there is no need to track the alert type at all, so the IIRecordViewControllerAlert enum, property, and assignment can all go away, reducing some friction for adding new instances of UIAlertView. This in turn means there is no longer a way for the NSInternalInconsistencyException to occur, because there is no longer a property whose value can be incorrect. Simply put, there is less code to maintain, and there are fewer places where things could go wrong.

There are some additional fringe benefits. The most important one is that the code that produces the UIAlertView, and the code that handles the UIAlertView, are lexically very close together. The call to [self.record delete] is actually inside the didTapDelete method. Similarly the call to [self sync] appears inside the method body of syncEngine:didFailForRecord:. This helps maintainability because there is a direct relationship between generating the UIAlertView and handling it. Putting the handler code in a generic method like alertView:clickedButtonAtIndex: hurts maintainability, because the UIAlertView machinery is only incidental to the actual purpose of the code: deleting objects and retrying sync. Chances are if you want to understand or change how delete confirmations work, you'll first go to didTapDelete. Not alertView:clickedButtonatIndex:.

It's true that the code would be shorter if the API were specific to UIAlertView. But by making IIDelegate generic, I was able to use it for UIActionSheet, UITableView, and other protocols. There is nothing stopping anyone from writing IIDelegate-alikes (or subclasses) for specific protocols to save some of that verbosity.

Implementation

The implementation of IIDelegate combines a collection of different systems. Mastering all these APIs was a fun way to learn some of the guts of both the Objective-C language and its runtime.

When you construct an IIDelegate instance, the first thing it does is create a new, anonymous class. At runtime! This is certainly unusual for Objective-C code, but we use this technique frequently in Moose. Runtime class construction was a perfect fit for this problem, so it was natural to bring that over to Objective-C.

Like Perl, there's no way to have a truly anonymous class in Objective C. The way we work around that is the same in both languages: we generate a class name at runtime, including a serial number. Once we've picked the class name, it is very easy to construct a new class at runtime.

objc_allocateClassPair([IIDelegate class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0);

That returns to us a new instance of Class that we can then flesh out.

We start by marking this new anonymous class as conforming to the protocols specified as parameters. That's as simple as calling class_addProtocol(self, protocol) in a loop.

We then install the specified blocks into the anonymous class as methods. This was by far the trickiest part of the whole process. To add a method to a class, you need to know the exact C types of its arguments. This is because Objective-C must use the C calling conventions to invoke methods. After all, if you don't know exactly how to put the arguments on the stack, there's no way you can call a function.

Getting the types out of a method is a royal pain. There is apparently no way to retrieve the argument types for a block. Every time I scour the APIs but I come up empty. I nearly threw away the entire project because of this limitation.

Eventually, the solution came to me. Since we are constructing objects to fit a specific list of protocols, we can inspect the protocols to retrieve the types of their methods. There's a function called protocol_getMethodDescription that takes a protocol and selector (and some other parameters that don't matter too much). It returns a struct of type objc_method_description, which contains a selector and a string of types.

To install a method in the class, we have to iterate over the list of protocols and see which protocol offers the desired selector. This is a ridiculous amount of labor, but it does work in the end. The only real limitation is that you can't add blocks to a class willy-nilly. Every method you add must match a selector in a protcol. This is a perfectly acceptable compromise for IIDelegate, since it is all about implementing protocols. Providing a method not in the list of protocols throws an exception.

Finally, now that the class has been fleshed out with its protocols and methods, we can finalize the class. This gives Objective-C a chance to perform whatever voodoo (perhaps optimizations?) is needed to promote our specification into a real, bona fide class. We must do this before we can instantiate objects of that class. And object instantiation, of course, is the last step in this process.

In the end, we were able to take a list of protocols, and pairs of selectors and blocks, and make a class and object from whole cloth. I was quite impressed that this was possible at all, let alone without any particularly egregious hacks. I very much encourage you to read through the code to see that it's not very complex at all.

But, Objective-C's metaobject protocol is finicky to work with. This is partly because it's a C API, so you have to manage memory yourself (free? really?) and deal with functions that provide vital information to you by assigning to pointers you provide. But it certainly lets you get the job done. It would be great if Apple could provide a nice Objective-C API over the core runtime calls; [class protocols] would be vastly preferable over the C mess that you have to wade through today. This would let you easily subclass Class and as we've learned from Moose, that offers developers incredible power.

Problems

There are potential problems with IIDelegate. Some of them I've been able to solve; some not! I'd love to hear from you if you have any ideas.

Weak

The standard convention for delegate properties is to declare them as (weak). This avoids retain cycles, since it is often the case that the delegate has a strong reference to the object that is delegating to it. For example, a view controller might have a strong reference to a UITableView, which in turn would have a reference to that view controller since it serves as the UITableViewDelegate. By making the reference from the table view to the view controller weak, everything can be neatly cleaned up.

The problem is that IIDelegate objects can be very short lived. In the UIAlertView example, your delegate object would have two references to it: the strong reference from the IIDelegate *delegate lexical variable, and the weak reference from the UIAlertView instance. Well, once the method that creates the delegate returns, the only reference to it is weak. This means the delegate is freed long before the user taps on one of the UIAlertView buttons, so your block will not be executed.

The only complete fix I can think of would be to adopt a garbage collector in Objective-C. Which is absolutely a nonstarter. Apple has rightly put all its chips into ARC instead.

One quick, hacky solution is to just maintain a strong pointer to the IIDelegate object somewhere, then release it when the block is invoked. I'm not happy with it, but the old way with the nightmare switch statement needed a similar hack, so we just can't win.

Selectors

The other problem is was much smaller in scale: providing method names as strings just plain sucks. Using the @selector(…) syntax saves memory (like a symbol in Ruby does), can be compile-checked, and the IIDelegate internals uses selectors (via NSStringToSelector) anyway. And practically, you can tab complete method names inside @selector(…). The reason that the version 1 API expects strings is because NSDictionary requires keys implement the NSCopying protocol, which SEL does not (it's not even an NSObject). I recently decided the way forward was an API that let you build up a delegate in pieces.

Class class = [IIDelegate delegateClassForProtocol:@protocol(UIAlertViewDelegate)];
[class addSelector:@selector(alertView:clickedButtonAtIndex:)
       withImplementation: ^(id _delegate, UIAlertView *alertView, NSInteger buttonIndex) {
    if (index == [alertView firstOtherButtonIndex]) {
        [self.record delete];
    }
}];
id<UIAlertViewDelegate> delegate = [class finalizeDelegate];

…

Because this API does not involve a collection object, we can use SEL directly.

This also reduces a few level of indentation, and hopefully helps enable new ways to build up these IIDelegate objects.

Memory

You might be concerned that creating classes at runtime would produce memory leaks. Wouldn't all those classes stick around when their one instance goes away? Thankfully, the Objective-C runtime provides an undocumented function3 called objc_disposeClassPair that is used to deallocate classes. -[IIDelegate dealloc] calls this to clean up after itself. There are even tests to make ensure that creating and releasing thousands of delegate objects doesn't cause memory to grow unbounded.

Conclusion

Protocols are fantastic. I use them all over the place. The way Apple uses protocols in its APIs has definitely flavored how I write even Perl code nowadays. But now that Objective-C has support for blocks, it makes sense to use them in place of protocols for very simple APIs.

Apple has yet to provide a block-based API for UIAlertView or UIActionSheet. That is hardly a problem because Objective-C gives programmers so much power that they can build their own block-based APIs.

IIDelegate is my attempt at building a generic protocol-to-block adaptor. Its implementation uses Objective-C's metaobject protocol extensively, for better or worse. I hope that, even if you don't use IIDelegate itself, it can still teach you that protocols, blocks, and metaobject protocols are all fantastic tools that every Objective-C developer should become familiar with.


  1. Many programmers shy away from such systems nowadays, since abstracting away the network introduces tight coupling and makes it difficult to cope with errors. Waldo et al note in "A Note on Distributed Computing" that "Differences in latency, memory access, partial failure, and concurrency make merging of the computational models of local and distributed computing both unwise to attempt and unable to succeed." But I'm sure that during NeXT days, networked objects were extremely cool. ↩

  2. This is a terrible method name, because user interaction in iOS is done through taps, not clicks! ↩

  3. Why. I. Never! ↩

Can Infinity change how you spend nights & weekends?

t: 800.646.0188