312

What's the exact reason for using dispatch_once in the shared instance accessor of a singleton under ARC?

+ (MyClass *)sharedInstance
{
    //  Static local predicate must be initialized to 0
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

Isn't it a bad idea to instantiate the singleton asynchronously in the background? I mean what happens if I request that shared instance and rely on it immediately, but dispatch_once takes until Christmas to create my object? It doesn't return immediately right? At least that seems to be the whole point of Grand Central Dispatch.

So why are they doing this?

Erik Godard
  • 5,930
  • 6
  • 30
  • 33
Proud Member
  • 40,078
  • 47
  • 146
  • 231

2 Answers2

424

dispatch_once() is absolutely synchronous. Not all GCD methods do things asynchronously (case in point, dispatch_sync() is synchronous). The use of dispatch_once() replaces the following idiom:

+ (MyClass *)sharedInstance {
    static MyClass *sharedInstance = nil;
    @synchronized(self) {
        if (sharedInstance == nil) {
            sharedInstance = [[MyClass alloc] init];
        }
    }
    return sharedInstance;
}

The benefit of dispatch_once() over this is that it's faster. It's also semantically cleaner, because it also protects you from multiple threads doing alloc init of your sharedInstance--if they all try at the same exact time. It won't allow two instances to be created. The entire idea of dispatch_once() is "perform something once and only once", which is precisely what we're doing.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 4
    For the sake of the argument I need to note that [documentation](https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once) doesn't say it's being executed synchronously. It only says that multiple simultaneous invocations will be serialized. – expert Aug 25 '13 at 21:28
  • 5
    You claim that it's faster - how much faster? I have no reason to think you're not telling the truth, but I'd like to see a simple benchmark. – Joshua Gross Oct 01 '13 at 18:30
  • 30
    I just did a simple benchmark (on iPhone 5) and it looks like dispatch_once is about 2x faster than @synchronized. – Joshua Gross Oct 01 '13 at 18:44
  • And what is bad on singleton like this ? Considering I do not access from multiple threads... static MyClass * _instance; + (MyClass *) instance { return !_instance ? _instance = MyClass.new : _instance; } – Renetik Dec 08 '13 at 12:17
  • 3
    @ReneDohan: If you are 100% certain that nobody ever calls that method from a different thread, then it works. But using `dispatch_once()` is really simple (especially because Xcode will even autocomplete it into a full code snippet for you) and means you never even have to consider whether the method needs to be thread-safe. – Lily Ballard Dec 09 '13 at 00:41
  • it is certainly faster than @synchronized, but will not faster than static variable assignment in initialize method. – siuying Mar 14 '14 at 07:30
  • 2
    @siuying: Actually, that's not true. First, anything done in `+initialize` happens before the class is touched, even if you aren't trying to create your shared instance yet. In general, lazy initialization (creating something only when needed) is better. Second, even your performance claim isn't true. `dispatch_once()` takes almost exactly the same amount of time as saying `if (self == [MyClass class])` in `+initialize`. If you already have an `+initialize`, then yes creating the shared instance there is faster, but most classes don't. – Lily Ballard Mar 14 '14 at 22:31
  • 1
    @siuying: Also, the performance win is completely pointless. I did benchmarking yesterday of `+initialize` and `if (self == [MyClass class])` vs `dispatch_once()` and found that, on arm64 on an iPad Air, both took just over 2000ns the first time, and just over 1600ns the second (when not under contention, `dispatch_once()` is faster on the second call, and calling `[MyClass class]` is faster once `+initialize` has finished, for various reasons). 2000ns is pretty darn negligible. – Lily Ballard Mar 14 '14 at 22:33
  • 1
    Came across this benchmarking that shows `dispatch_once()` is faster: http://bjhomer.blogspot.com/2011/09/synchronized-vs-dispatchonce.html – James Kuang Jun 06 '14 at 14:47
  • @Incyc: Than `@synchronized`? Absolutely! Dramatically faster, no question about it. The recent discussion in the comments here was comparing it to using `[MyClass class] == self` in `+initialize`, which is where there's not such a clear-cut answer. – Lily Ballard Jun 06 '14 at 17:41
  • @expert: The current docs say _“If called simultaneously from multiple threads, **this function waits synchronously** until the block has completed.”_ – Slipp D. Thompson Jun 26 '17 at 11:09
  • @SlippD.Thompson So, technically, it does ***not*** say wether it is synchronous if called from only one thread. Apple could have been more precise in the definition. – fishinear Aug 06 '18 at 12:58
  • @fishinear Okay sure, those docs aren't perfectly clear.  But in lieu of that, you can always test it for yourself or — better still — read the open source code for `dispatch_once()` at https://github.com/apple/swift-corelibs-libdispatch/blob/master/src/once.c .  You'll find it's quite synchronous.  _So I'm taking the stance with a fair amount of evidence for it being synchronous and zero evidence for it being asynchronous, it's a safe & reasonable assumption to make that **`dispatch_once()` is always synchronous** and counter-arguments have little more than F.U.D. backing them._ – Slipp D. Thompson Aug 06 '18 at 13:49
  • 1
    @SlippD.Thompson Neither 'expert' nor I were arguing that it could be asynchronous, it most definitely is not. We were making the argument the documentation is incomplete, and so it is understandable that somebody (like the OP) could misunderstand it. – fishinear Aug 06 '18 at 13:56
41

Because it will only run once. So if you try and access it twice from different threads it won't cause a problem.

Mike Ash has a full description in his Care and Feeding of Singletons blog post.

Not all GCD blocks are run asynchronously.

Abizern
  • 146,289
  • 39
  • 203
  • 257