Archive for March, 2007

Selectors and performance anxiety

Sunday, March 25th, 2007

As I sit on my front porch looking at the 73° temperature (K, since I live on Neptune) in the weather widget on my new MacBook Pro, I can’t help but think that life is good. Yet there’s a nagging voice in the back of my head, reminding me of unresolved issues. Wait, that’s the cat wanting to come outside. In addition, I hear the echoes of Jim Kerr. Let’s forget about them, though, and talk about -[NSObject performSelector:withObject:afterDelay:]. This has absolutely nothing to do with my previous post, honest. You can trust me, because we Neptunians (Neptunites? Neptuners?) cannot lie. We’ve been known to exaggerate, but only once every billion years or so.

The question on all of our minds, at least those of us not suffering from March Madness (to be followed no doubt by April Antisociality and May Melancholy), is what happens when you call performSelector:withObject:afterDelay: with a delay of (NSTimeInterval)0.0 during application launch. It turns out that you can call this successfully pretty much anytime, even during -[NSApplication init], in which case the currentRunLoop will be created immediately rather than during -[NSApplication setServicesMenu:]. It’s also safe to call this as late as during the delegate method applicationDidFinishLaunching:. Regardless of when you call performSelector:, the selector will be performed during -[NSApplication nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:kCFRunLoopDefaultMode dequeue:YES] — not the first one, which returns an NSApplicationActivatedEventType, but the one after that. The second one returns an event of type NSFlagsChanged, but I’m not sure whether that matters.

If you call performSelector: multiple times during launch, the selectors will be performed consecutively, during the very same nextEventMatchingMask:, in the order in which they were set up to be performed. Everything seems to happen before any user events — keyboard, mouse, interface licking — are processed. Thus, my conclusion is that -[NSObject performSelector:withObject:afterDelay:0.0] is a safe and effective way of scheduling methods to be performed after application launch but before the user starts wrecking stuff and wreaking havoc. Of course, I also believed that beer was a safe and effective antiseptic, so take my conclusion with a grain of salt … just not in your wound.

Remember, Jim, ice cream is a dish best served cold.

Everything you always wanted to know about NSApplication

Saturday, March 10th, 2007

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.