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

305

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

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

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

Гордый член
источник
Note: static and global variables default to zero.
ikkentim

Ответы:

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

Лили Баллард
источник
4
Ради аргумента я должен отметить, что документация не говорит, что он выполняется синхронно. Это только говорит о том, что несколько одновременных вызовов будут сериализованы.
эксперт
5
Вы утверждаете, что это быстрее - насколько быстрее? У меня нет причин думать, что вы не говорите правду, но я бы хотел увидеть простой тест.
Джошуа Гросс
29
Я только что сделал простой тест (на iPhone 5) и похоже, что dispatch_once примерно в 2 раза быстрее, чем @synchronized.
Джошуа Гросс
3
@ReneDohan: Если вы на 100% уверены, что никто не вызывает этот метод из другого потока, тогда он работает. Но использовать dispatch_once()это действительно просто (особенно потому, что Xcode даже автоматически заполняет его до полного фрагмента кода для вас) и означает, что вам даже не нужно думать, должен ли метод быть потокобезопасным.
Лили Баллард
1
@siuying: На самом деле это не так. Во-первых, все, что делается, +initializeпроисходит до того, как к классу прикасаются, даже если вы еще не пытаетесь создать свой общий экземпляр. В общем, ленивая инициализация (создание чего-либо только при необходимости) лучше. Во-вторых, даже ваше заявление о производительности не соответствует действительности. dispatch_once()занимает почти точно такое же количество времени , как говорят if (self == [MyClass class])в +initialize. Если у вас уже есть +initialize, то да, создание общего экземпляра происходит быстрее, но большинство классов этого не делают.
Лили Баллард
41

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

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

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

Abizern
источник
Лили - лучший ответ, но я оставляю свою, чтобы сохранить ссылку на пост Майка Эша.
Абизерн