I’m trying to understand the necessity of the class initializer? As stated right above, “default values set in Root.plist in the Settings.bundle” is higher priority than “default values set with registerDefaults: (lowest)”. Commenting the entire class initializer out maintains the exact same behavior. Is this serving some other purpose? I could understand if you weren’t using the Settings.bundle and wanted to set the defaults in the initializer instead, but in this case, loading them from the plist file manually and registering them doesn’t seem to make any sense?
Well, not really. Comment out the + initialize method and uninstall the application from the simulator (or device) and run it again. The first time you try it, there won’t be any saved preferences and -stringForKey will return nil.
Check something like this in -application:didFinishLaunchingWithOptions:
NSString *messageString = [ud stringForKey:@"BNRMessagePrefKey"];
messageString = @"You all kind of smell!";
I know it looks stupid when you think about it and I must admit when I saw that +initialize method in the book, my first reaction was WTF. I have like 50 books about iPhone development and not a single one so far explains what happens with NSUserDefaults the first time you run your application and how to actually use those DefaultValues people naively put into Root.plist. It’s amazing and hard to explain why Apple did it this way, why aren’t DefaultValues automatically used the first time you run the application!?
Check out the version in the downloaded code for the book, it has few minor tweaks. Anyway, this chapter additionally proves how outstanding and useful this book actually is.
When you ask NSUserDefaults for a value for a key, it looks at five different places until it finds a match.
If you run the application from the command line, you can specify the values for (zero or more of) your preference keys. These key-value pairs are added to the NSArgumentDomain. The NSArgumentDomain is searched first when you ask NSUserDefaults for a value. If NSUserDefaults finds a value for a key in the NSArgumentDomain, it returns that and searches no more. However, if it does not…
It moves on to the Application domain. These are all of the values you have written to NSUserDefaults during the course of your application ([[NSUserDefaults standardUserDefaults] setObject:… forKey:…]). These values are persistent, that is, they are written to disk and read from disk by your application. These are stored in ~/Library/Preferences/com.yourcompany.yourapplication.plist. Of course, if it does not find a value for a key in the Application domain…
It moves on to the Global domain. These are preferences that Apple has specified and you can change in your system settings. If the value for a key isn’t there either, it moves on to the Languages Domain.
If the value doesn’t exist for the key there or any of the other places, it moves on to the NSRegistrationDomain. The registration domain is the fallback for preferences for your application, you can think of them as the factory settings out of the box. The registration domain, however, does not persist. So, if you want a registration domain for an application, you must set the contents of the registration domain every time the application launches. You do this with registerDefaults:.
The reason we place it in +initialize is because +initialize will be called before any messages get sent to that class or instances of that class. Therefore, we are guaranteed to have a valid registration domain before we execute any code that may use NSUserDefaults.
Thanks for all these details. I was trying to point out the fact that DefaultValue specified in the plist file is not used by the system. Instead, you need to use it yourself in your own code and that was what you did in +initialize method.
Searching for more information in the documentation didn’t help me much, but there is a possibility that this all situation is result of installing our application from Xcode. Maybe when application gets downloaded from the AppStore it uses all those DefaultValue values from Root.plist and creates initial preferences. As I said, I’m just guessing here. I have no idea what is that key ‘DefaultValue’ designed for?
Ah, that key is used by the Settings application to set the initial value. If you aren’t exposing any preferences in Settings, you can register your default settings any way you want - you don’t have to use the key DefaultValue. You don’t even have to use a file to store them in, you can programmatically generate a dictionary in +initialize and send that to registerDefaults:.
Well, yes. But I have always assumed that it will be used as well the first time my application runs and asks NSUserDefaults for a keys value. Your book not only cleared what is actually going on, but it gave me a simple solution for it.
When I looked back in other books dealing with this issue, I realized some have methods that are again initializing in code all those values to the same thing specified as DefaultValue in the plist file. One book even states that we should be careful to set the same values in code as we did in plist file. This is so error prone. They have something like this:
NSMutableDictionary *defs = [NSMutableDictionary dictionary];
[defs setObject:@“Initial value” forKey:@“Some Key”];
[[NSUserDefaults standardUserDefaults] registerDefaults:defs];
As I realized why we need +initialize method, it became clear that you gave us universal solution for all future projects. In other words, I have assumed something like +initialize method is already built into NSUserDefaults class by Apple, and the guy who started this thread seems to have thought the same. Now, with your help things are finally as we mistakenly expected all along.
Not to belabor the point, but I am still confused about the priority of the default settings, specifically, #2 on p. 330:
2. default values set in Root.plist in the Settings.bundle
Reading the above posts, it seems like these default values never get read in by your app, unless you use the code in the +initialize method. But isn’t that covered by #3?
3. default values set with registerDefaults:
At first I thought that maybe these Root.plist defaults were being used by the Settings app. So for example, if you went under the Settings and changed one preference value, it would write out all of them and they would be used next time your app launched. But this doesn’t seem to be the case - it only writes out the values that actually change.
So, in my mind, the priorities (disregarding other priorities like NSArgumentDomain, etc.) should really be something like:
- preferences set by the user in Settings (the default values from Root.plist are used in the Settings app for the initial values)
- default values set with registerDefaults: (which we need to manually read in from Root.plist each time the app launches)
Is this correct?
An interesting and enlightening discussion! It does seem that the #2 priority item on p293 of “default values set in Root.plist in the Settings.bundle” is incorrect if NSUserDefaults doesn’t automatically create these settings as you’d expect.
So to check I’ve got this straight: in the initialize method we’re manually getting the array of dictionaries from the Root.plist file and registering them as the standardUserDefaults of NSUserDefaults. This is necessary because bizarrely the class doesn’t register them itself from the default values in the Root.plist file. Internally, the standardUserDefaults looks for user-defined keys first (which I’m guessing it must save separately) and then it falls back on the defaults.