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


305

在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;
}

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

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


Note: static and global variables default to zero.
— ikkentim's

Answers:


418

dispatch_once()是绝对同步的。并非所有的GCD方法都是异步执行的(例如,dispatch_sync()是同步的)。使用的dispatch_once()替换以下惯用法:

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

这样做的好处dispatch_once()是速度更快。从语义上讲,它也更干净,因为如果它们都在相同的确切时间尝试,它还可以保护您免受多个线程执行sharedInstance的alloc init的攻击。它不允许创建两个实例。整个想法dispatch_once()是“一次执行某件事”,这正是我们正在做的事情。


4
为了论证,我需要注意的是文档并没有说它是同步执行的。它只是说多个同时调用将被序列化。
— 专家

5
您声称它更快-快多少?我没有理由认为您没有说实话,但是我希望看到一个简单的基准。
— 2013年

29
我只是做了一个简单的基准测试(在iPhone 5上),看来dispatch_once比@synchronized快约2倍。
— 2013年

3
@ReneDohan:如果您100%确信没有人从其他线程调用过该方法,那么它将起作用。但是使用dispatch_once()确实非常简单(特别是因为Xcode甚至会为您自动将其自动完成为完整的代码段),这意味着您甚至不必考虑该方法是否需要线程安全。
— 莉莉·巴拉德

1
@siuying:其实不是。首先,+initialize即使您还没有尝试创建共享实例,任何完成的事情都会在触摸类之前发生。通常,惰性初始化(仅在需要时才创建内容)会更好。其次,即使您的表现要求也不正确。dispatch_once()采用几乎完全相同的时间相同的话if (self == [MyClass class])在+initialize。如果您已经有了+initialize,那么可以,创建共享实例的速度更快,但是大多数类却没有。
— 莉莉·巴拉德

41

因为它将只运行一次。因此,如果您尝试从不同的线程两次访问它,则不会造成问题。

迈克·阿什(Mike Ash)在他的《单身人士的护理和喂养》博客文章中有完整的说明。

并非所有GCD块都异步运行。


莉莉(Lily's)是一个更好的答案,但我要离开我,保持与迈克·阿什(Mike Ash)帖子的链接。
— 阿比森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.