Rewriting a Public Cocoa Touch API

Cocoa Touch added API for presenting a view controller in a popup bubble in iPhone OS 3.2, the responsible class is named UIPopoverController. One would guess that this new class is a subclass of UIViewController, just like UINavigationController is, but that is not the case. One would also guess that in functionality many ideas for displaying a view controller as modal controller would have been moved over to displaying a view controller as a popover controller, but that is not the case either.

Modal View Controllers are Easy

Displaying a modal view controller is quite straight forward as this small example shows:

Popover View Controllers Can be Complex

Displaying the same details view controller in a popover on iPad is not quite as straight forward:

As you see presenting a popover requires an extra object instance, this instance is not automatically retained while presenting the popover, like a modal view controller is, so the instance must be saved somewhere. In this example I saved the instance as a property. Notice that I also register as a delegate for the popover in order to remove this instance when the popover is dismissed. Here is another detail that is not obvious; this delegate callback is only called if user action dismisses the popover, not if you programmatically dismiss the popover. In the end using popovers quite quickly introduces a lot of code, and also coupling between view controllers for no other reason but to manage the popover instance.

Would it not be nice if you could instead do like this, and be done with it:

In fact what we want is to have these methods added to the public API of UIViewController:

But That Requires Rewriting Existing Classes?

Yes it does, in an ideal world we want new methods and properties on the existing UIViewController class, and all subclasses, just as if Apple had put them there. This can easily be done thanks to the dynamic nature of Objective-C.

The view controller that is going to present other view controllers in a popover needs to have access to all UIPopoverController instances currently visible. The view controllers that are going to be presented in a popover need to have access to their container UIPopoverController, and for consistency with modal view controller its parent view controller.

Categories can only add and replace methods on an existing class, not modify instance variables. So we need to store this information in another way. I choose to tuck them away in a helper class called CWViewControllerPopoverHelper. With this small interface:

The implementation is just synthesized properties, so no need to duplicate that one here.

So now all we do is to stuff one instance of CWViewControllerPopoverHelper into a global map using the UIViewController as key for each view controller that needs these extra instance information.

Fetching the Popover Helper

Let’s implement a helper method that lazily creates a CWViewControllerPopoverHelper instance and puts it into the global map when needed. This way any view controller that do not need to support popovers will never be bothered, and those who do manage popovers gets one instance with no fuzz:

Proper Memory Management

There is one problem with storing the extra information in a global map; no view controller would ever be released from memory, since the map will hold on to the references. No problem, lets not use the actual view controller instance as a key, but a NSValue with the pointer to the instance. This solves the problem of releasing objects, but also means that the map will contain dangling pointers to released objects. We need to remove the dangling pointers whenever a UIViewController is deallocated.

Subclassing is not an option, since it would defeat our purpose of requiring the public API. It turns out we can replace the -[UIViewController dealloc] method with our own, and still call the original implementation. Each class and category that is loaded into the Objective-C run-time will call their own method +[? load] method, it is the public hook for further modifying a class or category before use. We want to use this hook to replace the default deallocation method with our own:

As a bonus we also replace -[UIViewController parentViewController], to return the parent as would be expected for anyone who has ever presented a modal view controller. The implementations of our overrides are just as short and sweet:

Wrapping Up

From here on it is just a tedious work to implement all 50 more lines of code needed to actually present the popovers, the first method as an example:

The rest of the implementation, and a few other nice to have methods can be downloaded here.

Conclusions

The dynamic nature of Objective-C means that you can rewrite any existing API to suit your needs, without having access to the original source code. Sometimes just to add a new utility function where it belongs; a “sort by first name” method should probably be added to the actual people collection class, not as a spurious utility class.

Or as shown in this example to make a public API more convenient to use.

3 Comments

  1. R CSD

    it seems that my question isn’t related to your topic, but I wonder if it is possible to get cocoa touch API and using it on windows environment as I am working on project that aims to develop iPhone apps on windows environment
    so could I get and use cocoa touch API?

  2. @R CSD: Short answer is no, Mac OS X is a requirement for iOS development. You can get it to work with vmware, but I would not recommend trying to go down that rout; there be dragons.

Trackbacks for this post

  1. iPad Programming Roundup at Under The Bridge

Leave a Reply