Everything you always wanted to know about NSApplication

It seems that waiting for me to write a new blog post is like waiting for Larry David to write a new TV show. By the way, season one of Lap Cat Software Blog is available now on DVD for the low price of free ($35 shipping and handling). For those of you who don’t care for reruns, the wait is finally over: today is the season two premiere blog post! If you recall, in last season’s finale I was abducted by aliens and replaced by an evil clone. As we begin this season, I’m still the clone, but I’ve mellowed and become Lieutenant Governor. Confused?

I was confused about calling -[NSObject performSelector:withObject:afterDelay:] during application launch. Specifically, I was interested in a delay of (NSTimeInterval)0.0. When exactly, if ever, would the selector be performed? Is there a chance that user events could intervene? According to the documentation, The run loop for the current thread must already be running and in the default mode (NSDefaultRunLoopMode) for aSelector to be sent. For the purposes of investigation, I created an Xcode test project. In typical overkill fashion, I decided that getting to the bottom of the issue would require overriding every public method of NSApplication, NSMenu, NSRunLoop, NSThread, and NSWindow, along with some selected super and protocol methods. I threw in NSAutoreleasePool for good measure. In my main(), I put the following commands before the call to NSApplicationMain():

[[JJApplication class] poseAsClass:[NSApplication class]];
[[JJAutoreleasePool class] poseAsClass:[NSAutoreleasePool class]];
[[JJMenu class] poseAsClass:[NSMenu class]];
[[JJRunLoop class] poseAsClass:[NSRunLoop class]];
[[JJThread class] poseAsClass:[NSThread class]];
[[JJWindow class] poseAsClass:[NSWindow class]];

There was probably an easier way to do this, but I did what I did. I yam what I yam. You can download my project. As usual, it’s released under the SHAG license.

Leaving out many details, here is the basic sequence of events when you launch a Cocoa application. Your results may differ, so talk to your doctor before betting your life on this sequence.

  1. +[NSApplication sharedApplication].
  2. -[NSMenu initWithCoder:]
  3. -[NSWindow initWithContentRect:styleMask:backing:defer:]
  4. -[NSApplication setMainMenu:].
  5. -[NSApplication setWindowsMenu:].
  6. -[NSApplication setServicesMenu:].
  7. awakeFromNib.
  8. Enter -[NSApplication run]. This is the point of no return. That is, you never return from this method.
  9. Enter -[NSApplication finishLaunching].
  10. NSApplicationWillFinishLaunchingNotification.
  11. -[NSApplication makeWindowsPerform:@selector(_registerDragTypesIfNeeded) inOrder:NO].
  12. Return from -[NSApplication finishLaunching].
  13. Enter -[NSApplication nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:kCFRunLoopDefaultMode dequeue:YES].
  14. -[NSApplication activateIgnoringOtherApps:NO].
  15. NSApplicationDidFinishLaunchingNotification. Notice that the app already returned from finishLaunching a while ago (relatively speaking).
  16. -[NSApplication makeWindowsPerform:@selector(_visibleAndCanBecomeKey) inOrder:NO].
  17. Return NSEvent with type NSAppKitDefined, subtype NSApplicationActivatedEventType, from -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:].
  18. Enter -[NSApplication sendEvent:] with the same event.
  19. NSApplicationWillBecomeActiveNotification.
  20. NSApplicationDidBecomeActiveNotification.
  21. Return from -[NSApplication sendEvent:].

A few interesting notes:

  • NSThread. As you would expect, all of the application’s methods are called on the one, main thread. I’ve defined the macro JJTHREADLOGGING to verify this. For the main thread, which is returned by +[NSThread currentThread], none of the methods +[NSThread alloc], +[NSThread allocWithZone:], or -[NSThread init] are ever called.
  • NSAutoreleasePool. An autorelease pool is not created until after -[NSApplication finishLaunching] returns. If you call autorelease in your application delegate method applicationWillFinishLaunching:, an autorelease pool will not be created! Memory leak, anyone? The methods +[NSAutoreleasePool allocWithZone:] and -[NSAutoreleasePool init] are called many times, but they always return the same object, while -[NSAutoreleasePool release] and -[NSAutoreleasePool dealloc] are never called.
  • NSRunLoop. The run loop for the main thread is created in -[NSApplication setServicesMenu:]. Strangely, the run methods for the run loop ( -[NSRunLoop acceptInputForMode:beforeDate:], -[NSRunLoop limitDateForMode:], -[NSRunLoop run], -[NSRunLoop runMode:beforeDate:], -[NSRunLoop runUntilDate:] ) are never called.

If you’d like to try my project for yourself or modify it, I’d be very interested to hear about what you learn. I haven’t really investigated application termination, for example. You may have noticed that I also haven’t addressed how -[NSObject performSelector:withObject:afterDelay:] works during launch. I’m such a tease! These questions — and many others — will be answered in the next episode.

One Response to “Everything you always wanted to know about NSApplication”

  1. I like to picture this post prefaced with “Previously on Lap Cat Software…”