views:

1288

answers:

4

Hi,

I have defined a constants class within my iphone program using the 'extern' and 'const' keywords as in the example described in:

http://stackoverflow.com/questions/538996/constants-in-objective-c

At this point, I am trying to initialize some string constants from the contents of a plist file, instead of being defined right in the class, e.g., instead of having:

// Constants.m
NSString * const MyConstant = @"a constant";

I would like to have it initialized somewhere from the plist file. So far, I have done a test using the static +(void)load method, but I am not completely happy about it, e.g.:

// Constants.m
NSString * ALERT_QUIT_TITLE;

@implementation Constants

+ (void)load {
// this controller contains all the strings retrieved from the plist file
    LabelsController *labels = [LabelsController instance];     
    ALERT_QUIT_TITLE = labels.alertQuitTitle;     
}

@end

Using a log call I can verify that the load code gets called early in the app startup, even before the AppDelegate constructor. However, two things I see not good in this approach:

  1. I have to remove the 'const' keyword, otherwise I get a compile error since I am trying to initialize a variable that is defined as constant
  2. I get some sort of warning message about the autoreleased pool:

*** _NSAutoreleaseNoPool(): Object 0x50b330 of class NSPathStore2 autoreleased with no pool in place - just leaking Stack: (0x905caf0f 0x904d8647 0x904e039f (etc)

I guess I could use a direct call to the Labels controller to retrieve the label, but I would like more to treat it like a constant having all the maint. advantages it provides.

Which would be the correct (recommended) way to initialize a constant from an external source, like in this case a plist? Hope you can help, I have lost a good few hours trying to resolve this!

Thank you in advance.

A: 

I would suggest using the NSUserDefaults method instead for storing data.

Nick
A: 

If you initialize from a plist file, then you do not have a constant. And you should not define it as such.

I am guessing what you want is to be able to treat this value as if it was a constant? And that can be achieved using lazy initialization instead.

NSString* AlertQuitTitle() {
  static NSString* title = nil;
  if (title == nil) {
    LabelsController* labels = [LabelsController instance];     
    title = labels.alertQuitTitle;           
  }
  return title;
}

Is there a good reason as to why you do not use the NSLocalizedString() macro to fetch the alert quit title?

The warning

As the warning states, you are executing the +load method outside of an auto release pool. Meaning that all calls to autorelease just leak memory. You can fix your method like this:

+ (void)load {
// this controller contains all the strings retrieved from the plist file
  NSAutoreleasePool* pool = [NSAutoreleasePool new];
  LabelsController *labels = [LabelsController instance];     
  ALERT_QUIT_TITLE = labels.alertQuitTitle;
  [pool release];
}
PeyloW
A: 

Load is called far too early in the process for most purposes. Even initialize is fairly early. As you've noted, there is no autorelease pool setup, so any use of it (which is quite hard to avoid) will give you warnings and possible leaks.

A better way to do it is to forget the constant entirely, and write LabelController alertQuitTitle to lazily initialize its database and cache its answer. Something like this (untested, uncompiled).

+ (NSDictionary*) labelStrings;
{
    static NSDictionary* strings = nil;
    if ( !strings ) {
        // Allocate and laod and keep ownership of the NSDictionary
    }
    return strings;
}

+ (NSString*) alertQuitTitle
{
    static NSString* alertQuitTitle = nil;
    if ( !alertQuitTitle ) {
        alertQuitTitle = [[LabelController strings] objectForKey:@"alertQuitTitle"];
    }
    return alertQuitTitle;
}

If you really want, you can convert alertQuitTitle into a macro and use that to easily create dozens of methods.

In your other code, if you really want to, you can write a method that caches the answer as well, but thats fairly pointless, instead just use [LabelController alertQuitTitle].

You can, if you prefer, use a singleton, but there is not much point even creating a single instance of LabelController unless you have other things for it to do - any data it needs can be stored as static variables. A singleton would be more inline with typical Cocoa behaviour though. Either way, the same technique will work.

Peter N Lewis
A: 

To directly answer your question, it looks like you're calling load before an NSAutoreleasePool has been set up. Every thread needs its own NSAutoreleasePool; your main thread's NSAutoreleasePool is set up in main.m, which you can see if you open up that source file.

I usually initialize my application's globals in my App Delegate's init method.

But this looks like unnecessary optimization, and it's creating problems as a result. You should consider using string resources for something like this. See NSBundle localizedStringForKey:value:table:, and NSLocalizedString()

Darren