ChatGPT解决这个技术问题 Extra ChatGPT

Why does Apple recommend to use dispatch_once for implementing the singleton pattern under ARC?

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?

Note: static and global variables default to zero.

T
Thomas Tempelmann

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.


For the sake of the argument I need to note that documentation doesn't say it's being executed synchronously. It only says that multiple simultaneous invocations will be serialized.
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.
I just did a simple benchmark (on iPhone 5) and it looks like dispatch_once is about 2x faster than @synchronized.
@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.
@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.
A
Abizern

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.


Lily's is a better answer, but I'm leaving mine to keep the link to Mike Ash's post.