Design Principles for Structuring iPhone Apps

One of the biggest hurdles between writing small examples and building large applications is the principles for organizing and structuring code. Learning how to implement complicated algorithms might make you a good computer scientist, and is rigorously taught in schools, but structuring large applications has always been more of a black art to me. Especially in writing code that interacts with the real world, since we now deal with things that happen asynchronously.

Thus, I’m putting together some of the major design principles in iPhone programming. I’ve been writing a music player app over the December holiday, and much of the software design comes from previous projects, reading the iSoul and Dropbox API code, and reading StackOverflow, and here I’m putting some ideas together.

MyAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
...
[window makeKeyAndVisible];
return YES;
}

Firstly, Cocoa-based apps all start off with an AppDelegate. This is your main entry point into the world of iPhone and Cocoa apps, and here is our first and only place to store state for the duration of the program (I’m about to extend this idea, so bear with me). The AppDelegate is the entry from where Controllers (handling logic) is launched and long-running state is handled. This leads us to two main ideas:

Model-View-Controller

Cocoa follows the idea that views (displayed to the user) is separate from business logic and data management (written in Controllers) and data representation (Models). This means, your app will contain plenty of controllers responsible for both handling user interaction and handling interaction with data storage and data sources.

A controller can be an object responsible for talking to a REST API, or an object responsible for presenting a view to the user and capturing mouse and keyboard interaction with this view. I personally like to distinguish between these two by calling the former Managers and the latter ViewControllers, but they both fit into the MVC model the same way. I might get some flak on this, because people will disagree with me whether a data source is a model, a controller, or something else. I prefer the controller nomenclature since I tend to consider data managers and view controllers structurally identical.

A model is a concise description of a piece of data. The controller talking to a remote API might return an array of model objects. My code might contain a Person object (a model), a PersonManager (a controller responsible for managing person objects) and a PersonEditViewController (a controller responsible for presenting views that modifies a person object). Why this separation? Because now I can encapsulate the generic person logic into the Person model, the storage logic into the PersonManager, and the front-end logic into the ViewController.

Now I have all these controllers, managing data and presenting views, floating around. Structuring them and storing state brings me to the second idea:

Dependency Injection (and Singletons)

How do we manage global state? Or, in the case of our example, how does the PersonEditViewController work with the PersonManager to get a Person model object, display it to the user, capture the user’s edits, and store the new Person object?

The PersonManager can be made into a singleton object. Now, anywhere in the code, [PersonManager sharedManager] can be called and we can get the instance of this Manager. We can imagine “getPerson” and “savePerson” functions on this instance, and we’re off to the races. This is a great way to structure libraries, since the global library initialization code can be captured in some singleton instance, and we can use this library anywhere. In fact, this is how the heart of the Dropbox API is structured – on app load, a DropboxSession singleton instance is created, which captures the authentication details of your app. Anywhere in your code that needs to dropbox access starts off by getting the singleton instance of the DropboxSession and works from there.

Singletons break down the otherwise nice object-oriented nature of our code and makes testing much harder, since we now cannot unit-test objects with dubby instances of a singleton without modifying the singleton creation code. Singletons are no different from global variables – they break the modularity of your system, and makes code harder to read and harder to reason about. The alternative is dependency injection. Initially we said that our AppDelegate is our only place to store shared state. Clearly, singletons allow state as well, but a more modular approach is using the AppDelegate to place major long-running state. So, we place our PersonManager object (the controller responsible for managing Person model instances) as an instance variable on our AppDelegate, and we inject it into any other controller that needs to access it – PersonEditViewController now has a “setPersonManager:” call or an “init:withPersonManager:” constructor. Structuring code this way makes dependencies perfectly clear and allows for unit testing by passing dummy dependencies into an object.

Now that we have  a way to structure out code in general, we need to manage data flow between these controllers. This leads us to talking about delegation and callbacks.

Delegation and Target-Action (Callbacks)

Both these approaches encapsulate the idea of communication through callbacks. Delegation is the approach of having a specific object – the delegate – be responsible for all the callbacks from the host object. Target-action is an approach where the host object can inform many “delegates” about behavioral changes. Let’s keep this rooted in practice.

Whenever an object creates a new object to do a specific task for itself (for example, a Controller creating a View to present to the user, or a Controller creating a socket connection to a remote api) the one-to-one communication between this worker and the original object can be asynchronously captured by having the main object as the delegate of the worker object. The worker object has a weak reference to its delegate, and can call methods on its delegate to communicate. These methods are captured in a common interface – in Objective-C by using Protocols, the equivalent to Java’s Interfaces. For example, the Dropbox API has a controller responsible for letting the user log into dropbox. You can create this controller at any point, set yourself as the delegate object, and display it to the user. When the login is done, your object’s succes or failure methods gets called:

Your controller:

- (void)didPressLink {
DBLoginController* controller = [[DBLoginController new] autorelease];
controller.delegate = self;
[controller presentFromController:self];
}

- (void)loginControllerDidLogin:(DBLoginController*)controller { [self updateButtons]; [self.navigationController pushViewController:photoViewController animated:YES]; }  - (void)loginControllerDidCancel:(DBLoginController*)controller { }
And, in the DBLoginController, you see things where the delegate gets called:
- (void)didPressCancel { [self setWorking:NO]; [self.navigationController.parentViewController dismissModalViewControllerAnimated:YES]; [delegate loginControllerDidCancel:self]; }

Delegation is especially nice, since you can define many callback methods as part of the protocol, implement the ones you care about, and simply register yourself as a delegate. “worker.delegate = self” makes all the methods you wrote available to the worker. The target-action approach we’re abou to see only connects a single method of yourself to a “worker” (bad nomenclature, sorry!), but everyone gets to join the fun.

So, delegates work great when a single worker is spawned to do something for an object – where some long-running object creates a worker to do something for it – but it does not work in the case where many objects want to know when something asynchronously happens. If we want one-to-many communication, we can’t just have a single delegate. We can still structure communication around a Procotol and setting up a list of callbacks, or use the slightly looser Target-Action approach. Here is the callback approach:

- (void)registerPlayerStateCallback:(id <PlayerStateCallbackProtocol>)callthis {
[_callbacks addObject:[callthis retain]];
}
- (void)notifyCallbacks {
NSEnumerator * e = [_callbacks objectEnumerator];
id <PlayerStateCallbackProtocol> callback;
while (callback = [e nextObject]) {
[callback playerStateChanged:_state];
}

}

These kinds of callbacks (or target-action) works especially well when you have some object that needs to know when long-running state changes. See the difference? When some long-running object spawns a worker to do something asynchronously, we use a delegate. If an object wants to know when (potentially long-running) state changes, the object registers an action of itself as a target of a state change. Yes, the two definitely overlap, but the one-to-one versus one-to-many differentiation helps in deciding which one.

Say, in our original example, a MainViewController shows a list of people in the system. This view should change whenever the PeopleManager’s internal list of people changes. Since PeopleManager was created as some shared state inside AppDelegate, and we will use dependency injection to pass the PeopleManager to the MainViewController, setting the MainViewController as the delegate of the PeopleManager will break any other code that also wants to be the delegate of the PeopleManager. This is easy to imagine – maybe there is a object that broadcasts to the web whenever the list of people changes, and that needs to get a callback from the PeopleManager as much as our front end view controller. Thus, we create a method locally that we want PeopleManager to call whenever its state changes, and we register the specific instance of the MainViewController object and the method we want it to call with the PeopleManager.

Notice that we can definitely use the Target-Action approach if we want one-to-one communication as well, and we can even sidestep the Protocol and register any method of an object as te receiver of some callback. From the Dropbox API:

@interface DBRequest
- (id)initWithURLRequest:(NSURLRequest*)request andInformTarget:(id)target selector:(SEL)selector;
@end

Naturally there are plenty more tricks to getting these things right. Reading code is probably the best way to learn how to structure large programs, but this here is a start. Hang your state off of your AppDelegate, use dependency injection to have state be accessible, set up objects as delegate of the things they spawn, and register objects as targets of events that happen. Access your stateful libraries through singletons and Boom! You’ve got yourself a maintainable, testable iPhone/Cocoa app.

As the experts can probably tell from this post, I am by no means an expert myself, so any feedback is welcomed in the comments!

4 Comments

  1. [...] This post was mentioned on Twitter by Dylan Beadle. Dylan Beadle said: Design Principles for Structuring iPhone Apps – http://bit.ly/hGEHGv [Good, short summary of good iOS coding patterns] /via @rizzledizzle [...]

  2. [...] Joubert presents some very good ideas in Design Principles for Structuring iPhone Apps. Your code will be better if you follow these [...]

  3. [...] EECS from the Trenches » Blog Archive » Design Principles for Structuring iPhone Apps. Niels Joubert wrote a nice introduction to applying good design principles to iOS projects. This entry was posted in Links. Bookmark the permalink. ← Passing Blocks in Ruby Without &block LikeBe the first to like this post. [...]

  4. Websites worth visiting…

    I enjoyed reading your article, many thanks….