The System Preferences application provides a convenient way to install a preference pane. Double-clicking the preference pane will prompt the user if he wants to install it for the current user only or for all users of the computer. Then System Preferences will copy the preference pane to ~/Library/PreferencePanes or /Library/PreferencePanes according to what was chosen asking for administrator password if necessary. Finally the preference pane will be loaded and presented to the user.
Now, let's see what happens when a preference pane is upgraded. Again, System Preferences is smart: it is able to detect if an older version of the same preference pane is installed and proposes to replace it [1]. Everything seems alright, but it is actually not! Things are more complicated if the preference pane to upgrade has already been loaded. That is, if the user already clicked the preference pane.
Indeed, preference panes are just a special kind of bundle which is loaded into System Preferences with the -(BOOL)[NSBundle load] method (cf. -(BOOL)[NSPrefPaneBundle instantiatePrefPaneObject] method of the PreferencePanes framework). The problem is that on Tiger, a NSBundle can not be unloaded. So when upgrading an opened preference pane, the old code is not unloaded and as a consequence the new code is not loaded. This is because System Preferences calls the -(BOOL)[NSBundle load] method which returns YES, meaning that the bundle was successfully loaded or that the code has already been loaded. In the case of an already opened preference pane, that's how the result of the loadmethod should have been interpreted. Unfortunately, it is interpreted as if the bundle was successfully loaded and System Preferences thinks it has loaded the new bundle, but it has not.
This is very problematic because at this point, the resources (nib files, pictures etc.) of the new bundle have already been copied. So we have the old code which is accessing the new resources. I let you imagine the numerous problems this situation can cause. At best, exceptions will raise and your preference pane will be half working. At worst, your preference pane will simply crash.
So, how do we fix this problem?
First, the preference pane must detect itself when it's upgrading over an older already loaded version as System Preferences does not detect it [2]. This must be done as early as possible, i.e. at the very beginning of the - (id)initWithBundle:(NSBundle *) bundle method. It is possible to detect this situation with the help of the version of your NSPreferencePane subclass. See mydetection snippet to understand how detection works.
Once this is detected, we must properly reload the new preference pane. This must be achieved by quitting System Preferences and relaunching System Preferences. This is the not that elegant solution to unload the old preference pane. The elegant solution would be to unload the bundle. This is left as an exercise to the Apple engineers for a future version of System Preferences.
Relaunching System Preferences and selecting the preference pane is quite tricky. A second executable must be responsible for relaunching the System Preferences application. Also, it is nicer for the user if the preference pane he just upgraded is automatically selected. Automatic selection of the pref pane is achieved through Apple Script. Please refer to my reload snippet for implementation details. Note that once you have compiled the reload executable, you have to place it inside the resources directory of your preference pane. Do not place it inside the executable directory (Contents/MacOS) if you do not want to see the reload application popping up in the Dock.
With this reload code in place, if the user ever happens to upgrade a preference pane while the older one was loaded, he will experience a System Preferences flicker as it will quit and reopen right away. While this might be surprising to him, this is still better than a half working preference pane or a crash.
If anything is unclear, just say it so in the comments and I will try to elaborate. If everything is clear, just pick up my code snippets and implement them in your preference pane as soon as possible ;-)
No comments:
Post a Comment