It’s not that it’s not called, it’s just not called when you would expect. There is a quirky behavior that you find when you have a window set to be ‘Visible at Launch’ in ASStudio projects. Under normal circumstances, awake from nib handlers are called for all objects in the application and in the view hierarchy before any other handlers are called (see the awake from nib documentation for the order in which launch-time handlers are called). This is great because you can usually do all of your initializations safely without worrying about whether or not other references you make in other objects’ awake from nib handlers will be valid. BUT…when you have a window that is set to be visible at launch, it is actually called to become visible even before the ‘will finish launching’ handler of the app… LONG before the other objects awaken in the normal course of their own awake from nib handlers. It is possible, although uncommon, to have specific circumstances cause problems with your references, because objects aren’t initializing in a predictable manner. You could end up making a reference to an object that hasn’t awakened from nib yet, and you get an error or it skips the statement and you get a null reference. The best way to work around this problem, is to never set your windows to be visible at launch, and to display them manually in your code using the launched handler of the app instead. This way you can be certain that the objects have been created in the proper sequence.
Let’s look at a the sequence of events given the simple example of a window being shown at launch time, first using the visible at launch and then using the launched handler to do it manually…
1. Visible at launch
- ‘opened’ (window) ← Not good
- ‘will finish launching’ (app)
- ‘awake from nib’ (app)
- ‘awake from nib’ (window)
- ‘awake from nib’ (other objects)
- ‘launched’ (app)
2. Using launched handler
- ‘will finish launching’ (app)
- ‘awake from nib’ (app)
- ‘awake from nib’ (window)
- ‘awake from nib’ (other objects)
- ‘launched’ (app)
- ‘opened’ (window)
As you can see, the window opens even before it’s own awake from nib handler is called. This could really mess you up if you get sequence 1 when you’re assuming sequence 2.
Here’s a practical example…
Create a window (“Window”). Add a button to it (“Button”). Connect the window to its ‘awake from nib’ and ‘opened’ handlers. Connect the app to its ‘launched’ handler. In Xcode, add a property variable for the button reference. Get a reference to the button in the awake from nib handler of the window. In the opened handler, try to set the title of the button using the reference.
property theButton : null
on awake from nib theObject
if name of theObject is "Window" then
set theButton to button "Button" of theObject
end if
end awake from nib
on opened theObject
set title of theButton to "Schmoodge"
end opened
on launched theObject
show window "Window" --> Use this instead of the 'Visible at launch' option
end launched
Now, to test the problem we’re discussing, set the window to be visible at launch. When you run the app, you’ll get an error, stating that you can’t set the title of null. Since the reference to the button is created long after the opened handler is called, there’s no object in the variable to set the title of. Now, add the code to display the window to the launched handler, and disable the visible at launch option. You should get the desired result.
This doesn’t just happen in the ‘opened’ handler, it can effect you at unexpected times when you make calls to other handlers or subroutines, and can be difficult to troubleshoot. You may ask why I use the launched handler to open the window, and not some other handler. You could show the window in it’s awake from nib handler too, but you still may run into problems if you have other references to make. The launched handler is “safe”, because if you set up your initialization properly you should be able to be certain that all of your objects’ references are sound before you officially “launch” the app. i.e. show it to the user and let them start using it. Notice that the “other objects” awaken from nib after the window does. The awake from nib process should be hierarchical, so it starts at the highest object and works it way through all of the subviews.
Try moving the ‘show window…’ line in the launched handler to just before the ‘set theButton…’ line in the awake from nib handler. Ka-pow! No go, huh? The button hasn’t awakened from nib on it’s own terms yet. Now put it after that line, and it should work. Making a call to the button first has caused it to be instantiated, so it does exist after you’ve made a hard-coded reference to it. Beware, though, that it too… like the window… may not be officially awakened from nib yet, it may just be instantiated and may not have fired it’s awake from nib handler. These are weird technicalities that can only be discovered through trial and error, and will make you pull your hair out until you learn of them the hard way.
Logically, you would expect that the window must be awakened before it’s ever displayed on the screen. Since it’s not in this circumstance, you just need to be aware of it and plan accordingly. My point was that you have to have a really good grasp of how everything in your app is initialized and configured to avoid these quirks. While sometimes it’s nice to simply click a button and have everything work magically, there are sometimes odd side effects or conflicts you may not anticipate. Being verbose, thorough, and sometimes doing things the hard way can be more intelligent and reliable.