views:

42

answers:

2

Hi,

which is the cleanest way to use something like a global variable? Normally, using a global variable is forbidden, but I don't know a better solution for accessing NSUserDefaults from different classes.

I read a bit and come up with this. I define a Contants.h and a Constants.m file and include them everywhere I need to.

 //Constants.h
 #import <Foundation/Foundation.h>


 @interface Constants : NSObject {
  extern NSUserDefaults *settings;
 }

 @end

.

 //Constants.m
 @implementation Constants

 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"plist"];
 NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
 [[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
 NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];

 @end

The problem here is that I want to initialize a value to my constant. I have no method in Constants.m. So my helper variables would also be globals?

One thing to mention: I think the global variable also has to be released?

Thanks for your help!

Edit:

@hotpaw2:

AppBundleSingleton.h:

#import <Foundation/Foundation.h>


@interface AppBundleSingleton : NSObject {

}

+ (AppBundleSingleton *)sharedAppBundleSingleton;

@end

AppBundleSingleton.m:

#import "AppBundleSingleton.h"


static AppBundleSingleton *sharedAppBundleSingleton = nil;


@implementation AppBundleSingleton

#pragma mark -
#pragma mark Singleton methods

+ (AppBundleSingleton *)sharedAppBundleSingleton {
    @synchronized(self) {
        if (sharedAppBundleSingleton == nil) {
            sharedAppBundleSingleton = [[self alloc] init];
        }
    }
    return sharedAppBundleSingleton;
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedAppBundleSingleton == nil) {
            sharedAppBundleSingleton = [super allocWithZone:zone];
            return sharedAppBundleSingleton;  // assignment and return on first allocation
        }
    }
    return nil;  // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (NSUInteger)retainCount {
    return NSUIntegerMax;  //denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}

-(id)init {
    self = [super init];
    sharedAppBundleSingleton = self;
    // Initialization code here
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"plist"];
    NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    [[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];

    return self;
}

@end

In my AppDelegate.m I have the following:

// ...
#include "AppBundleSingleton.h"


@implementation MyAppDelegate

// ...

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch.

    // Add the navigation controller's view to the window and display.
    [window addSubview:navigationController.view];
    [window makeKeyAndVisible];

    [AppBundleSingleton sharedAppBundleSingleton];

    return YES;
}
// ...
@end

In my ViewController I query the values:

NSString *myString = [[NSUserDefaults standardUserDefaults] stringForKey:@"myKeyforString"];

Would that be a solution?

+3  A: 

You don't need to use global variables - you can access the userdefaults from any class or object within your project like this:

BOOL foo = [[NSUserDefaults standardUserDefaults] boolForKey:@"bar"];

As long as you synchronize the userdefaults after every change you make, the data fetched in this way is consistent.

Toastor
I want to initialize my NSUserDefaults with this NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"plist"]; NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath]; [[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict]; Where do I do that?
testing
@testing in you appDidFinishLaunching: method
Dave DeLong
appDidFinishLaunching may or may not work. some views which require defaults to initially configure themselves may get called before appDidFinishLaunching.
hotpaw2
+1  A: 

Global variables are not only NOT forbidden, but a required part of the ANSI C specification, which is a subset of Obj C. Both gcc and llvm fully support globals. They are often the smallest and fastest way to pass unprotected values around.

That said, explicit use of global variables are most probably not the solution to your problem. You have a problem quite well suited to the MVC paradigm. Place all your NSDefault code into a singleton model class (an M of the MVC), which can self-initialize on first access (the implementation may use a hidden global), and attach that object to the appDelegate where it can easily be obtained from anywhere. Then encapsulate all you default value read/writes as properties of that object.

hotpaw2
I edited my question. I've now created a Singleton with a init method, which initializes my NSUserDefaults from a plist file. I don't need a global variable anymore. I access my instance of the Singleton in didFinishLaunchingWithOptions. Is that right? It works so far, but you mentioned that "some views which require defaults to initially configure themselves may get called before appDidFinishLaunching". How to attach my singleton to appDelegate without using didFinishLaunching? I'm directly calling NSUserDefaults, so obtaining from anywhere is already possible.
testing
The self-initializing process is done with the call of sharedAppBundleSingleton and the written init method. What do you mean with the encapsulating stuff? Do I need these properties? I'm already doing the initialization of my default values in the init method of the Singleton.
testing
Is anybody here?
testing