Conventional Cocoa wisdom holds that each window in an application should be in its own nib/xib file. The benefits are shorter launch times, better memory usage, and better Xcode project organization. Conventional wisdom is always right … right? We cynics know better. True wisdom is knowing when conventional wisdom is wrong.
I haven’t posted in awhile (especially anything Cocoa related) but the subject showed up on my radar again. What bothers me is this generalization: if you’re not (always) segregating your windows with nibs, you’re (always) doing it wrong. I have to take a stand. Blind adherence to generalizations will get you in trouble.
Let’s consider the approach. The basic idea is that every window in your app is placed into its own Interface Builder (nib) file. This coincidentally forces you to use one NSWindowController per window, which in turn forces you to keep that window’s UI management logic separate from other windows’ UI management and so on. For larger applications (which implies larger Xcode projects) this has performance and organizational benefits.
Note the term “basic idea” – this is where rote advice repeaters can fail their listeners. I’ll admit it: I make this mistake myself. If you repeat a bit of advice often enough, you become lazy. You carelessly overgeneralize. This can result in misguiding others. We’ll get to that part in a bit. For now, let’s have a bit more detail.
If you’re not familiar with the concept of “lazy loading” you’re doing yourself and your users a disservice. Lazy loading means only loading resources when the user’s actions demand the resource. That is, only load the UI that’s needed now. In the case of the “monolithic nib” – where the entirety of your application’s UI is in a single nib file – your application takes longer to launch and uses more memory whether it needs to or not because it’s loading everything. This is often unnecessary.
A favorite example is the preferences window. A user is unlikely to adjust the application’s preferences every time they use the application. It just doesn’t happen and we know this. Many apps have toolbar-driven, multi-pane preferences windows (another “is-it-really-necessary” discussion begging to be written). Does all that need to be loaded at launch time? No. It’s better that the preferences UI be loaded only when the user requests it for the first time.
Another example is the really complex application. Complex panels, such as Xcode’s Utility Area, can come with entire populations of sub-panels and may never be used during a session. If you’re any kind of sane, there’s at least one top-level NSViewController that manages such a panel but in reality, an extremely complex application may have view controllers for each sub-panel as well. For something as large and complex as Xcode, it’s a good idea not to load all of this unless the user asks for it.
All but the smallest, simplest of apps usually have at least one opportunity for lazy UI loading of a unit. The agreed-upon major unit is a window and its contents; a view is the minor unit. Anywhere you see a UI unit that may not be seen during a user’s session is an excellent opportunity to segregate and therefore to save time, memory, and even battery power. iPhone users will thank you for the latter.
In fact I’ll go a step further by saying anywhere you see an NSWindow and NSWindowController pair or an NSView and NSViewController pair that may not be used is an opportunity to optimize.
An opportunity. Remember the specificity of that word.
From the developer’s perspective, simply maintaining a large Xcode project offers its own set of challenges.
Good organization is more than just satisfying OCD. A well-organized project makes it easier to find things months or years down the road when you revisit a part of your app you haven’t needed to in awhile. Believe me, this long-time-no-see scenario happens often when you have more than one app. Or when your app is complex. Or when you’ve come back from vacation. Or when you finally got over being sick to death of even looking at that damn app.
Organization is especially important when working on a team or when you pass on the torch to another developer. This is similar to the need for readable, well-commented code. You can’t remain intimately familiar with every facet of your large project. Your future self (or your replacement) will thank you for making things easy to parse later.
Putting the preferences window in PreferencesWindow.xib (or whatever clear and concise naming convention you choose) makes it easy to find the related UI. Together with groups (the yellow folders in Xcode), the project is more easily navigable. You have a reasonable minimum of three files to maintain: PreferencesWindowController.h, PreferencesWindowController.m, and PreferencesWindow.xib. Put these in a group called “Preferences” and you can keep the group collapsed until you need to work on the preferences system. Put other preferences-specific source files there (like view/view-controller files if it gets really complicated) and you stay organized.
Zen and the Art of Xcode Project Organization.
Another organizational benefit is fault tolerance. Although it’s a less frequent occurrence these days, corrupted nibs have bitten nearly every Cocoa developer. Like the segmented hulls of modern ships, the puncturing of one segment shouldn’t sink the ship. Similarly, one corrupted nib in a well-segregated project causes less of a mess than that monolithic nib into which you stuffed the entire universe.
This is much less of an issue if you’re using a source code management system like Git or Subversion and make frequent, task-oriented commits. You are using source code management, aren’t you? Even so, there’s still risk of unnecessary loss of work if a nib is corrupted when you’re changing a lot of your UI at once. In the monolithic nib, all those changes are gone if the nib croaks; with multiple nibs, you only lose the work done in the corrupted nib. It’s a marginal benefit but you’ll be glad for it if the situation rears its ugly head.
The point I’ve been coming to is this: it’s possible to take it all too far.
Consider a small utility app. It has one main window that’s always visible and a secondary window (let’s say a floating palette, despite their decreasing popularity). Neither window is particularly complex and the secondary window is likely to be invoked during each (possibly every) session. The use of multiple nibs here is ridiculous. I’ll tell you why.
The user launches the app to perform a task. The MainMenu.xib file is read and the UI is loaded. The task likely involves a utility palette. The app is user-friendly so the palette remembers its shown-or-not-shown state from the last session and needs to be loaded. Now there’s a second round-trip to the disk to read its nib file and load its UI.
If you know the utility application will never become the next Photoshop, your blind “future-proofing” is utterly pointless. You’ve increased your application’s load time and disk activity for nothing. You followed the “conventional wisdom” you heard summarized somewhere without considering why you were doing it and in this case it was the wrong thing to do.
You’ve also added more files to your Xcode project. Did it help? Did it make your small, simple project easier to navigate and to maintain? I’ll bet not. Interface builder helps you extract and segregate UI from a single nib; you’re on your own if you want to merge nibs. From an organizational standpoint, over-segregation can be a burden. Just navigating between several interrelated nib files can become cumbersome. It now involves tabs and assistants. It may soon involve satellite surveillance and an operations team. Think The Bourne Identity.
Is it all really necessary? For medium-to-large-complexity apps, you’re sure to benefit at least a little. For small projects, you might be hurting performance and maintainability. Remember the word “opportunity”? It’s not the same as “requirement”. Always consider the units of measurement (window/controller pairs and view/view-controller pairs) and the likelihood those units will be used in each session.
In short: prefer true wisdom to conventional wisdom and think before you segregate.