Skip to main content

Mobomo webinars-now on demand! | learn more.

While iOS projects have the advantage of multiple NIB files, this is not the default for development on OSX. When working on a Mac or iOS project with more than one person, you quickly learn that attempting to merge conflicted Interface Builder files or XCode project files can only result in tears. But just because you can't work on the same NIB doesn't mean that the productivity of the entire team should be blocked by the one person editing MainMenu.xib. Cocoa allows you to chop your UI into separate NIBs and control them with multiple NSWindowControllers. Once you separate out different windows from MainMenu, you're much less likely to conflict with your team. As an added benefit, your UI will feel snappier because NIB loading will be delayed until it's actually needed. I'll demonstrate this technique by separating the Preferences window from the main window, a common and easy case for refactoring.

Code

For starters, let's create a new NSWindowController subclass for driving our Preferences window. We'll name it PreferencesController.

The header:

#import <Cocoa/Cocoa.h> @interface PreferencesController : NSWindowController { } @end 

The implementation:

#import "PreferencesController.h" @implementation PreferencesController  - (id) init {   if(self = [super initWithWindowNibName:@"Preferences"]) {   }   return self; } 

The only difference from a generic NSWindowController is the custom constructor. This controller will try to load a NIB named "Preferences.xib" when it's -showWindow: method is called. In the Xcode sidebar, right click Resources, Add File, User Interface, and choose "Window XIB". Name this xib "Preferences.xib".

Interface Builder

Next comes the error-prone step. If you don't add all the right connections in Interface Builder, then your new window will act erratically. It might not show up, it might not be in focus, it might not close, or it might explode your Mac (unlikely, but not impossible). First, add an NSObject to your Document and change the 'Class' to 'PreferencesController'

Preferences Controller Identity-2

To test that our NIB is loading properly, let's connect the 'Preferences' menu item to the showWindow: action.

Menu Item Connections

We're almost there, but if you run the app now, you'll notice that the Preferences window doesn't focus properly. While our "MainMenu.xib" has a reference to PreferencesController, we forgot to let Preferences.xib know that its owner is of type PreferencesController. Open "Preferences.xib", and change "File's Owner" to PreferencesController, and also set its "window" connection to point to the window.

Preferences Controller Connections

If you Build and Run the project now, you should be able to open the Preferences window from the menu and have the 2nd Preferences window loaded. Open and close the Preference window a few times for good measure too. If something is acting funny, the most likely culprit is a missing connection for "File's Owner" in "MainMenu.xib" or a missing connection for the menu. Go over the steps again and recheck your class identities and connections (cmd-5) in the inspector to make sure everything is wired correctly.

What's Next?

From here, whenever you need to make changes to the Preferences window, no changes need to be introduced to "MainMenu.xib". Controller actions can be specified on PreferencesController, and Interface Builder can access those actions by making connections to "File's Owner". For a demo, check out this account preferences demo. Hopefully, you can use this process in your project to cut down on nasty merges.

Resources

Categories
Author

From the Framework Programming Guide

  • Frameworks group related, but separate, resources together. This grouping makes it easier to install, uninstall, and locate those resources.
  • Frameworks can include a wider variety of resource types than libraries. For example, a framework can include any relevant header files and documentation.
  • Multiple versions of a framework can be included in the same bundle. This makes it possible to be backward compatible with older programs.
  • Only one copy of a framework’s read-only resources reside physically in-memory at any given time, regardless of how many processes are using those resources. This sharing of resources reduces the memory footprint of the system and helps improve performance.

Now that we've got a handle on what frameworks are let's set about building one. Open up Xcode and create a new project. We'll use the "Cocoa Framework" template which can be found under the "Mac OSX" group.

Our framework will return a random Cary Grant movie quote so we're going to name it "CaryGrantQuotes". This framework will have only one method which is defined in Quotes.m. Since it's such a simple framework, we can remove all of the frameworks listed under "Linked Frameworks" and "Other Frameworks" save for Foundation.framework.

Next, expand the "Targets" group located in the sidebar and expand "CaryGrantQuotes". Drag Foundation.framework to the group labeled "Link Binary With Libraries".

Header files are important because they tell an application what methods they can access. Headers that will not be directly accessible can be marked as "Project" but any header files that can be imported by an application need to be set to "Public". Since our project has only one header file, we'll need to set it to "Public". Under the group labeled "Copy Headers", right-click on the header files, select "Set role" ? "Public".

We'll need to modify our build settings so that the framework can be loaded properly by the enclosing application. Without getting into the gritty details of Xcode-specific environment details, the only ones you need to know about for getting this to work are @rpath and @loader_path. These will expand with the framework being loaded to point to the location on disk where the framework is located relative to the binary. Right-click on the CaryGrantQuotes item under "Targets" and select "Get Info". Adjust your settings to match those shown below.

Project settings

Your CaryGrantQuotes project should now match the image below.

Sidebar visual

Let's quickly build a Mac OSX application to embed our new framework in. We'll call it "MyProject" and all it will do is output a random quote to the debug console.

In the root of the newly-created MyProject directory, create a directory called "Frameworks" and open that directory using Finder. Tab back to the CaryGrantQuotes project in Xcode and build it.

Underneath the "Products" group, there should now be a "CaryGrantQuotes.framework" item listed. Right-click on that item and select "Reveal in Finder". Drag the newly-created framework into "MyProject/Frameworks" as shown in the image below.

Drag frameworks directory

We still have a few more tasks to perform before we can use this awesome new functionality. Under "MyProject", expand the "Frameworks" ? "Linked Frameworks" groups. Right-click on "Linked Frameworks", select "Add" ? "Existing Framework". This will take a few seconds to load as by default Mac OSX apps recursively search for all frameworks.

Since we are using our own framework, click on "Add Other…" on the bottom of the dialog, browse to the "MyProject/Frameworks" directory and select "CaryGrantQuotes.framework".

Next, expand "Targets" and right-click on "MyProject", select "Add" ? "New Build Phase" ? "New Copy Files Build Phase". Select "Frameworks" from the dropdown and close the dialog.

Now drag CaryGrantQuotes.framework from the "Linked Frameworks" group into this newly-created group which should be labeled "Copy Files". Then take this same group and drag it to a spot above "Link Binary With Libraries" (which should now contain the CaryGrantQuotes.framework item too).

MyProject sidebar visual

Nearly there! Now we need to let "MyProject" know where it can find this new framework. We do that by modifying the "Runtime search paths" value which will expand into @rpath when the binary is built.

MyProject rpath setting

Now all that remains is to add #import <CaryGrantQuotes/Quotes.h> to the top of MyProjectAppDelegate.m and we can use it how we see fit. To output a random quote to the debug console, stick this statement in applicationDidFinishLaunching:.

NSLog(@"Quote: %@", [Quotes randomQuote]);

Your debug console should now output a random Cary Grant movie quote, quite a useful feature. If, however, you receive the dreaded "Library not loaded; image not found" error, I suggest you get familiar with the command-line utility otool. Run the following command from the root of the "MyProjects" directory.

otool -l Frameworks/CaryGrantQuotes.framework/CaryGrantQuotes

The -l option shows you what libraries the framework is linked to as well as the path settings. The value of name under LC_ID_DYLIB should be @rpath/CaryGrantQuotes.framework/Versions/A/CaryGrantQuotes.

The entirety of the CaryGrantQuotes project can be found on github. Additional links and resources on embedding your own framework are listed below.

Categories
Author

My first foray into Objective-C was, for lack of a better description, a sink-or-swim situation. I was working for a previous employer and our lead iPhone developer had just been laid off; my old boss was in my office the next day asking me how quickly I could "get up to speed". "You know Ruby", he said, "How difficult could it be?" It was time to get some books.

The first point I would like to raise is that Objective-C, while itself quite elegant (at least in comparison to its namesake), is fairly useless on the Mac platform without the Cocoa framework. And it is this framework that I think a lot of Rubyists get hung up on. The other big sticking point is manual memory management through the use of retain and release.

Cocoa's roots start all the way back in the 1980s with the NeXTSTEP operating system which tagged along with Steve Jobs when he was tapped to lead Apple again in 1996. This is why Cocoa's core classes, such as NSArray and NSString, all begin with 'NS'. The naming scheme is a holdover from that earlier OS and while those two extra characters may not seem like much hassle, to a Rubyist they represent an unnecessary burden of verbosity. In addition, Cocoa makes extensive use of the delegate pattern, something that is rarely seen or needed in Ruby and can make it difficult to trace an execution path for those unfamiliar with the concept. One of the limitations of Objective-C, the inability to create difficulty in creating a function with a variable length argument list, is commonly resolved through the use of the poorly named hash userInfo, which frequently appears in method definitions without any connotation as to its purpose. And lest we forget those wonderfully verbose method names, I think even the most die-hard and grizzled veteran of Objective-C would agree that NSString's stringByReplacingOccurrencesOfString, could have been better-named.

Rubyists are proud of that fact that they don't have to worry about memory management. The more knowledgeable Rubyists could tell you that the garbage collector, or GC, works by continually scanning objects in memory once a process has accumulated eight megabytes worth, checking to see if there are any pointers to those objects and then releasing them back to the OS if they do not. But most Rubyists would refuse to venture any farther down that dark path of memory management out of a simple need to retain their sanity. Indeed, for a good few weeks I struggled with this concept until my fellow iPhone student Paul Barry introduced me to a book that would change my outlook. Titled "Learn Objective-C on the Mac", it proved to be a treasure trove of information on object allocation. Specifically, chapter nine, which dealt with memory management, made it crystal clear what was going on underneath the hood when an object was created, and thus retained, and when it was released. The concept itself is simple: retaining an object increases its "retain count" by one; releasing it reduces that count; and when it reaches zero that space in memory is released back to the OS. Immediately the seemingly-random crashes my applications faced were decipherable and easily fixed while my hostility to Objective-C and the Cocoa framework melted away.

As Rubyists, we tend to value the simple over the complex and prefer not to sweat the small stuff. Yet on a whole we also desire learning new concepts and many of us can attest to that being the driving factor behind leaving a former language of choice behind. On occasion, such as with Objective-C and Cocoa, our preference for simplicity and our desire to learn collide, head-on. But rather than tweet about how ugly Cocoa looks or how memory management in Objective-C is beneath you, I challenge you to dive further. After all, Ruby itself is built on Objective-C's forebear, C, and no programmer has walked away worse for wear after peeking under the hood. Learning Objective-C not only opens up the world of iOS application development but also makes us better Rubyists.

Categories
Author
1
Subscribe to Cocoa