views:

461

answers:

6
+1  Q: 

Singleton Design

I'm creating a game that uses cards. I have an AppController class with one instance in the nib. The AppController instance has an NSArray instance variable called wordList. On init, the nib's instance of AppController generates a new GameCard. Every gamecard has an array of words containing 5 words selected at random from the the list in AppController. Because the list is large, I'd like to read it into memory only once. Therefore, I want only one instance of AppController, as a singleton class. Every time a new GameCard is created from within AppController, it should access that same singleton instance to retrieve the wordlist. So basically, I need a singleton AppController that creates GameCards, where each GameCard has a reference to the original AppController. I'm not sure how to implement this. Sorry if the explanation was confusing.

A code example I found online follows (http://numbergrinder.com/node/29)

+ (AppController *)instance 
{
static AppController *instance;

@synchronized(self) {
 if(!instance) {
  instance = [[AppController alloc] init];
 }
}

return instance;
}

But when I tried to do something with it in a GameCard instance through the code below, my application took forever to launch and Xcode told me it was loading 99797 stack frames.

AppController *controller = [AppController instance];
+1  A: 

http://stackoverflow.com/questions/145154/what-does-your-objective-c-singleton-look-like

Stu
Hi. That's very similar to an example I just added to my post, but as you can see, there was a bit of a problem.
Walker Argendeli
+1  A: 

Hi Walker,

It sounds like you're on the right track. I've never tried to put a reference to a singleton in a nib file, though. You may want to create a separate singleton class that maintains a copy of the data (DataManager, maybe?), and then call it from within your instance of AppController to fetch the words.

You may find that putting a singleton within a nib (using the code for a singleton in Stu's post) works just fine, though. Good luck!

Ben Gotow
+3  A: 

It sounds like an infinite loop. Make sure that -[AppController init] isn't calling +[AppController instance].

Tom Dalling
This sounds very likely.
Chuck
Alternate explanation: -[AppController init] is creating the GameCards, each of which asks for the instance, which then inits another instance… (Three-point cycle instead of two-point cycle.)
Peter Hosey
+2  A: 

Why does every card need a reference to the app controller?

If it's just to access its words, it's simpler to let each card own its words directly. Make a new method named initWithWords: the designated initializer for the GameCard class. Initialize each card with the array of its five words, and have the card own that array for its lifetime.

Removing the cards' references to the app controller would resolve the infinite loop that Tom astutely detected.

Also, if no word should appear on two cards at once, remember to take that into account when drawing from the app controller's Great Big Array Of Words, and when destroying cards (you may or may not want the words to go back into the pile for future cards).

Peter Hosey
A: 

It looks like you may be calling your class instance method from within your init method. Try something like this:

static AppController* _instance = nil;

- (id)init
{
    // depending on your requirements, this may need locking
    if( _instance ) {
     [self release];
     return _instance;
    }
    if( (self = [super init]) ) {
     _instance = [self retain];
     // do your initialization
    }
    return self;
}

+ (AppController*)instance
{
    if( _instance ) return _instance;
    else   return [[AppController alloc] init];
}

This makes sure that only one instance of AppController is ever available and also that it's safe to allocate it as well as getting a copy through the instance class method. It's not thread safe, so if it's going to be accessed by multiple threads, you should add some locking around the checks to _instance.

Jason Coco
A: 

The normal way to create an AppController/AppDelegate is to add a custom NSObject to your MainMenu/MainWindow.xib file. Set the class to be AppController. Link your UIApplication/NSApplication delegate reference to your AppController object. Then you can get your single AppController with either

(AppController*)[NSApp delegate];

or

(AppController*)[[UIApplication sharedApplication] delegate];

You never have to create it with alloc/init because it will be created when your application is launched. You don't have to worry about making it a singleton because no one will ever try to create another one. And you don't have to worry about how to access it because it will be the delegate of the UIApplication/NSApplication object.

All that said, if you need a global variable holding an array of words, then forget about the AppController and make a new singleton object which holds/reads the array. In which case you just need:

+ (NSArray *)sharedWordListArray 
{
    static NSArray *wordList;
    if( !wordList ) {
        wordList = [[NSMutableArray alloc] init];
        // read array
    }
    return wordList;
}

If you really need thread safety, then simply call [WordList sharedWordListArray] from your app delegate's applicationDidFinishLaunching: method before starting any threads, or add an NSLock if you really want to defer the loading to later, but often its better to take the load time hit at the start of the program rather than unexpectedly when the user takes some later action.

Peter N Lewis