为什么Apple建议使用dispatch_once在ARC下实现单例模式?

Pro*_*ber 303 singleton objective-c ios automatic-ref-counting

在ARC下的单例的共享实例访问器中使用dispatch_once的确切原因是什么?

+ (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;
}
Run Code Online (Sandbox Code Playgroud)

在后台异步实例化单例不是一个坏主意吗?我的意思是如果我请求共享实例并立即依赖它会发生什么,但dispatch_once要到圣诞节才能创建我的对象?它不会马上回来吗?至少这似乎是Grand Central Dispatch的重点.

那他们为什么要这样做呢?

Lil*_*ard 416

dispatch_once()是完全同步的.并非所有GCD方法都异步执行(例如,dispatch_sync()同步).使用dispatch_once()取代以下习语:

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

相比之下的好处dispatch_once()是它更快.它在语义上也更清晰,因为它还可以保护您免受多个线程的影响,这些线程执行sharedInstance的alloc init - 如果它们都在相同的时间尝试.它不允许创建两个实例.整个想法dispatch_once()是"只执行一次",这正是我们正在做的事情.

  • 我只是做了一个简单的基准测试(在iPhone 5上),看起来dispatch_once比@synchronized快2倍. (29认同)
  • 你声称它更快 - 速度快多少?我没有理由认为你没有说实话,但我希望看到一个简单的基准. (5认同)
  • 为了论证,我需要注意[文档](https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once)并没有说它是同步执行的.它只说多个同时调用将被序列化. (4认同)
  • @ReneDohan:如果您100%确定没有人从其他线程调用该方法,那么它可以工作.但是使用`dispatch_once()`非常简单(特别是因为Xcode甚至会将它自动填充到一个完整的代码片段中),这意味着你甚至不必考虑该方法是否需要是线程安全的. (3认同)
  • @siuying:实际上,这不是真的。首先,在`+initialize` 中所做的任何事情都发生在类被触及之前,即使你还没有尝试创建你的共享实例。一般来说,延迟初始化(仅在需要时创建)更好。其次,即使您的性能声明也不是真的。`dispatch_once()` 花费的时间与在 `+initialize` 中说 `if (self == [MyClass class])` 的时间几乎完全相同。如果你已经有一个 `+initialize`,那么在那里创建共享实例会更快,但大多数类没有。 (2认同)

Abi*_*ern 41

因为它只运行一次.因此,如果您尝试从不同的线程访问它两次,它将不会导致问题.

Mike Ash在他的Care and Feeding of Singletons博客文章中有完整的描述.

并非所有GCD块都是异步运行的.