In the course of developing Speech Timer 2 for iOS and OS X, I’ve learned a number of lessons when making an app to run in both platforms. In essence, you can share much of the non-UI code but there’s a way to share some of the UI code as well. Here are my hard-earned lessons that you can take for granted:
- Separate the controller classes.
- Refactor common controller logic and share it whenever possible.
- OS X’s initialization order is different than iOS.
- Have a single document class for both platforms.
Controller Classes
In Cocoa’s Model-View-Controller (MVC) architecture, controller classes are really part of the user interface component and not really attached to model classes. Controller classes in iOS are derived from UIViewController
and in OS X you’ll need to subclass NSWindowController
The logic for each are significantly different and you’ll probably want iOS to have its own set of classes that are totally separate from their OS X counterparts – I wouldn’t recommend using the same class to handle both and switch between superclasses using preprocessor macros (more on this later).
Don’t fall into the trap of thinking that NSViewController
is an equivalent of UIViewController
It isn’t really. One of the big difference is that NSViewController
doesn’t participate directly in UI preservation & restoration. On the Mac, UI restoration is heavily leaned towards NSWindowController
and NSDocument
– you’ll need to handle NSViewControlle
restoration as special cases. However in iOS this is done primarily via UIViewController
.
Crafting Out Controller Logic
When you’ll developing the app, you’ll probably want to do one platform first and have it into a 90% working state before working on the other one. That way you’ll be able to identify more clearly which parts of controller code that are common to both platforms. Then you’ll can start refactoring to pull out these into it’s own classes that are shared by your UIViewController
and NSWindowController
subclasses.
The diagram above shows an example of how to architect these classes. On the iOS side, the iPad and iPhone gets their own view controller subclasses to handle their different UI paradigms. These view/window controllers are suffixed with “IOS” or “OSX” just to avoid naming clashes that will be useful when the two are contained within the same project – even though only one variant will be present for each build target. Moreover controller code that are identical to both iOS and OS X are refactored to their own “Logic” classes, which at runtime are owned by their respective controller classes.
Who owns the UI?
In iOS, the UI needs to be constructed first before the document – that is, UIViewController
objects need to be present and showing something before you can get a live UIDocument subclass. That’s because iOS’ user interface is still single-tasking for the most part; applications take up the entire screen and nothing else is appears to be running. Hence your app needs to be responsive and display something upon startup. You’ll need to have a root view controller present at applicationDidFinishLaunching
This also means that if you use UIDocument
with its asynchronous I/O, the document instance may not be ready when your UI is loaded.
Contrastingly OS X applications has the option to not show any UI before the document is ready. The menu bar will show up first, the app may open a document and then only show a window when the document is fully loaded. OS X’s multi-tasking UI doesn’t leave the user staring a blank screen while your application initializes and loads a document – the user probably have something else running in the meantime.
As a result, in iOS the UI object is “owned” by the application delegate. That is, the application delegate starts up the initial view controller and probably still has a reference to it – either directly or indirectly via the root UIWindow
object. But in OS X, the document tend to own most window controllers and also likely responsible for instantiating it – that’s why NSDocument
has a makeWindowControllers
method.
Sharing the Document class
Is the document class a UI component or a data store component? Chances are it’s a kind of both. It handles top-level local I/O in both iOS and OS X but in OS X it is also responsible to instantiate and keep a reference to window controllers. Hence you’ll probably want to have one document class that is shared in both OS X and iOS, and use preprocessor statements to handle each platform’s specific requirements:
@interface FOOYourDocument : #if TARGET_OS_IPHONE UIDocument #else NSDocument #endif // declare your custom methods here @end @implementation FOOYourDocument // your overrides here // OS X specific overridden methods #if !TARGET_OS_IPHONE -(void)makeWindowControllers { // instantiate your window controllers here. } #endif @end
If you use Core Data, you can use BSManagedDocument
on OS X in place of UIManagedDocument
on iOS. Furthermore, if you use Core Data, your document class would be pretty lean since the bulk of the work will be done either by Core Data or your data model classes, that will be virtually identical on iOS and OS X.
That’s all for now folks. Take care!
0 thoughts on “Tips for Architecting Dual-platform OS X / iOS Applications”
You must log in to post a comment.