Singleton Code Question

Discussion in 'Mac Programming' started by JohnMC, Jan 5, 2011.

  1. JohnMC macrumors 6502

    Joined:
    May 5, 2006
    Location:
    Duluth, MN
    #1
    I am working on an application that needs to share data in a plist between several different controllers. Apple has an example of what I want to do, but I don't understand the code they used. I did some digging yesterday and this morning, but I don't understand the dispatch_once_t pred & dispatch_once(&pred, ... stuff. My best understanding is that these lines stop the compiler from making more than one instance of the class, but I am not sure. Any help would be great, even if it is a document to read. Thanks.

    Apples' Code:
    // we use the singleton approach, one collection for the entire application
    static PeriodicElements *sharedPeriodicElementsInstance = nil;

    + (PeriodicElements *)sharedPeriodicElements
    {
    @synchronized(self) {
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{ sharedPeriodicElementsInstance = [[self alloc] init]; });
    }
    return sharedPeriodicElementsInstance;
    }
     
  2. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #3
    Code:
            static dispatch_once_t pred;
            dispatch_once(&pred, ^{ sharedPeriodicElementsInstance = [[self alloc] init]; });
    The code block
    Code:
    sharedPeriodicElementsInstance = [[self alloc] init];
    will be executed exactly once, when this particular call to dispatch_once is executed for the very first time. This is a common pattern for anything that you need to do exactly once in your code. dispatch_once has three advantages and one disadvantage: It is very fast if the code has already been executed, it is very simple to write because of the use of blocks, and it is thread safe, so if two threads tried to call the same call to dispatch_once at the same time, one would execute the code, and the other would wait without using the CPUs until the first thread has finished. Trying to do this kind of thing on your own usually is either slow, or not thread safe. Disadvantage: It requires 10.6.

    Unfortunately, the rest of Apple's sample code is rubbish. To do this correctly, get rid of the @synchronized (it is pointless), get rid of allocWithZone, and in the init method check whether the static variable sharedPeriodicElementsInstance is already set - if it is set, call [self release] and return sharedPeriodicElementsInstance. (That way, [[PeriodicElements alloc] init] will return the same object as [PeriodicElements sharedPeriodicElementsInstance], without calling init again).
     
  3. JohnMC thread starter macrumors 6502

    Joined:
    May 5, 2006
    Location:
    Duluth, MN
    #4
    Thanks Guys, I think I understand the code well enough to try using it. Robbie, no I had not seen that manuel yesterday, most likely because I was searching the iOS documentation, thanks for sharing it. Gnasher, thanks for explaining what was going on and why.

    John
     
  4. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #5
    Wow, the sample code actually wraps dispatch_once in @synchronized? O_O

    Could you post a link to that? I need to go file a bug...
     
  5. gnasher729, Jan 6, 2011
    Last edited: Jan 6, 2011

    gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #6
    http://developer.apple.com/library/...ents/Listings/Classes_PeriodicElements_m.html

    The code also tries to prevent creating another instance by overloading allocWithZone - that's the wrong place; the correct place is to do this in the init method. However, this all seems to be quite difficult, as

    http://www.cocoadev.com/index.pl?SingletonDesignPattern

    gets it wrong in various ways as well. It's a bit tricky to get it right completely without synchronisation (in case someone calls [[... alloc] init]). I'll post it later.
     
  6. gnasher729, Jan 6, 2011
    Last edited: Jan 6, 2011

    gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #7
    Writing a singleton class, using no synchronisation except for using dispatch_once, which is very cheap after the first call. Design goals: A singleton is returned by + sharedInstance, but [[... alloc] init] will also return the same singleton. All has to be threadsafe. And retain/release can be called but don't release the singleton. The tricky thing: init can be called either from within the dispatch_once call, or from any caller who did [[... alloc] init]. In the first case, self will equal sharedSingleton. In the latter case, sharedSingleton may be nil or not, but self will not equal sharedSingleton.

    Code:
    static MySingleton* sharedInstance = nil;
    
    + (MySingleton *)sharedSingleton
    {
        static dispatch_once_t pred;
        dispatch_once  (&pred, ^{sharedInstance = [self alloc]; [sharedInstance init]; });
        return sharedInstance;
    }
    
    - (id)init
    {
        if (self != sharedInstance)
        {
            [self release]; 
            return [MySingleton sharedSingleton]; 
        }
    
        id tmp = [super init];
        if (tmp == nil) { sharedInstance = nil; [self release]; return nil; }
        self = sharedInstance = tmp;
    
        // Your usual init code comes here. 
        return self;
    }
    
    
    - (id)retain { if (self != sharedInstance) [super retain]; return self; }
    - (NSUInteger) retainCount { return self != sharedInstance ? [super retainCount] : UINT_MAX; }
    - (void)release { if (self != sharedInstance) [super release]; }
    So what happens if you call [MySingleton sharedSingleton] ? On the first call, the dispatch_once code is executed. It allocates sharedInstance, then calls init. In the init method, self equals sharedInstance. So the code follows the usual init pattern, calling [super init] first, then doing whatever initialisation is needed, then returning self.

    If [super init] fails and returns nil, that is obviously trouble. [super init] would have tried [self release], but that wouldn't have worked because we don't release an object equal to sharedInstance. So we set sharedInstance to nil, release self again which will work this time because self doesn't equal sharedInstance anymore, then we return nil. If [super init] didn't return the original pointer, then whatever it returns will be the sharedInstance.

    On the second and further calls to [Mysingleton sharedSingleton], the same sharedInstance will be returned again.

    What happens if we call [[MySingleton alloc] init]? In the init method, self will not equal sharedInstance. So self gets released, and the value returned is the same as if [MySingleton sharedSingleton] had been called.
     

Share This Page