Почему Apple рекомендует использовать dispatch_once для реализации одноэлементного шаблона в ARC?

Какова конкретная причина использования dispatch_once в совместно используемом экземпляре для одноэлемента под 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;
}

Разве это не плохая идея, чтобы асинхронно создавать синглтон в фоновом режиме? Я имею в виду, что произойдет, если я запрошу этот общий экземпляр и сразу положиться на него, но dispatch_once занимает до Рождества, чтобы создать свой объект? Он не сразу возвращается? По крайней мере, это, как представляется, весь смысл Великой Центральной диспетчеризации.

Так почему они это делают?

Ответ 1

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() в том, что это быстрее. Он также семантически чист, потому что он также защищает вас от нескольких потоков, выполняющих функцию alloc init вашего sharedInstance - если все они пытаются в одно и то же время. Это не позволит создать два экземпляра. Вся идея dispatch_once() - "выполнить что-то один раз и только один раз", что и есть то, что мы делаем.

Ответ 2

Потому что он будет работать только один раз. Поэтому, если вы попытаетесь дважды получить доступ к нему из разных потоков, это не вызовет проблемы.

Майк Эш имеет полное описание в своем блоге Уход и кормление синглов.

Не все блоки GCD запускаются асинхронно.