苹果为什么建议使用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.