views:

159

answers:

3

Would you call this implementation of a multiton in objective-c 'elegant'? I have programmatically 'disallowed' use of alloc and allocWithZone: because the decision to allocate or not allocate memory needs to be done based on a key.

I know for sure that I need to work with only two instances, so I'm using 'switch-case' instead of a map.

#import "Multiton.h"

static Multiton *firstInstance = nil;
static Multiton *secondInstance = nil;

@implementation Multiton

+ (Multiton *) sharedInstanceForDirection:(enum KeyName)direction {

    return [[self allocWithKey:direction] init];
}

+ (id) allocWithKey:(enum KeyName)key {

    return [self allocWithZone:nil andKey:key];
}

+ (id) allocWithZone:(NSZone *)zone andKey:(enum KeyName)key {

    Multiton **sharedInstance;

    @synchronized(self) {

        switch (key) {
            case KEY_1:
                sharedInstance = &firstInstance;
                break;
            case KEY_2:
                sharedInstance = &secondInstance;
                break;
            default:
                [NSException raise:NSInvalidArgumentException format:@"Invalid key"];
                break;
        }
        if (*sharedInstance == nil)
            *sharedInstance = [super allocWithZone:zone];
    }

    return *sharedInstance;
}

+ (id) allocWithZone:(NSZone *)zone {

    //Do not allow use of alloc and allocWithZone
    [NSException raise:NSObjectInaccessibleException format:@"Use allocWithZone:andKey: or allocWithKey:"];
    return nil;
}

- (id) copyWithZone:(NSZone *)zone {

    return self;
}

- (id) retain {

    return self;
}

- (unsigned) retainCount {

    return NSUIntegerMax;
}

- (void) release {

    return;
}

- (id) autorelease {

    return self;
}

- (id) init {
    [super init];
    return self;
}

@end

PS: I've not tried out if this works as yet, but its compiling cleanly :)

A: 

Point of order: How do you know that you'll only ever have two instances, or need to have two instances? (Or want to have two instances?) What, exactly, is the point of having a "Multiton"? (And is that even a word?)

Williham Totland
A multiton is a design pattern that extends the concept of a singleton: http://en.wikipedia.org/wiki/Multiton_pattern
submachine
Here is an example of why one would want two instances (rather, its _my_ reason for wanting two):I want to access an sqlite3 database from two threads. Now, sqlite3 is thread-safe if one does not share connections across threads => I need to make sure each thread has its own connection. If an sqlite3 connection corresponds to an instance of a multiton and each thread can identify itself, then its a nice way to make sure that there is exactly one instance per thread.
submachine
Unless this is just a programming exercise, you might look for examples of how others have done sqlite threading - probably using simple mutexes instead of overly complicated constructs like multitons.
tedge
@tedge: You are right. I got a little carried away with it. I think I'll call it a programming exercise for now :)
submachine
I think implementing stdin/stdout/stderr is a reasonable excuse for a "multiton".
Gabe
@gabe: Not really. Why would you want that? They are, after all, just one-way streams, and you can just create the appropriate stream objects for them, you don't need a multiton for that. Singletons are great for when **by definition** you only need one of any given class within the context of your application (take, for instance, `NSApplication`). But if you need two, then you'll soon want three, and if you need 5, you'll soon want 10. In **any other case** than the *definition* singleton, you'll want to not do that. *Really*.
Williham Totland
*Ignoring the fact that there are better ways to implement stdio streams*, they're the perfect example of a multiton. Surely you agree that stdin is a singleton, as are stdout and stderr, right? There's no way you'll ever have 5 or 10 stdio streams because by definition there are exactly 3. So why not use a "multiton" for this?
Gabe
@gabe: Because files are also streams; stdin/out/err are just (moderatly) special cases of a regular file. Singletons they are most certainly not. They're just instances of the `FILE *` equivalent class.
Williham Totland
+2  A: 

Don't override alloc. The problem with overriding alloc to return a previously allocated instance of the class, as you do, is that when +sharedInstance calls [[Multiton alloc] init]... +alloc will return the old instance, then -init will re-initialize it! The best practice is to override -init, doing the cache lookup and calling [self release] before you return the cached instance.

If you're really concerned about the cost of that extra +alloc (it's not much), you also do your cache lookup in +sharedInstance and then ensure that all of your clients access the instance through +sharedInstance to avoid the extra alloc.

codewarrior
You are right. I was thinking of getting around it by making init smart enough not to re-initialize allocated instances (and add another bit of complexity to the code) Basically, I now get the feeling that the entire idea of forcing a multiton is bad.
submachine
+1  A: 

I find singletons a bad idea and this looks about four times as horrible. The code is quite complex, you can be sure of spending a nice few hours chasing subtle bugs in it and you will probably never feel comfortable about it. That’s no good. You should throw this abomination away and wire your objects together in some other way that doesn’t require so much thinking.

If you like patterns, you can use something akin to Factory pattern to wire your objects. The Factory will take care of creating those two instances and passing them wherever needed. And the Factory will be a lot more simple than Multiton:

@interface Factory : NSObject {
    Foo *foo1, *foo2;
}
@end

@implementation Factory

- (id) init {
    [super init];
    foo1 = [[Foo alloc] init];
    foo2 = [[Foo alloc] init];
    return self;
}

Of course you don’t have to create both instances at once. You can do anything you like there – cache, lazy load, anything. The point is leaving the Foo lifetime management up to the Factory, separate from the Foo code. Then it gets much easier. ¶ All the other objects that need Foo will be created and wired through Factory and will receive their Foo through a setter:

@implementation Factory

- (id) wireSomeClass {
    id instance = [[SomeClass alloc] init];
    [instance setFoo:foo1];
    [instance setAnotherDependency:bar];
    return [instance autorelease];
}

This is all much more straightforward then the code from your question.

zoul
It is true that its complex, and from what codewarrior points out in his answer, it will only get worse. And after reading this: [http://stackoverflow.com/questions/2410830/apple-singleton-example-query/2411685#2411685], I decided that I need to do something simpler. Forcing a multiton this way doesn't seem so elegant anymore.
submachine