Чому 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;
}

Хіба не погана ідея синхронізувати сингл асинхронно на задньому плані? Я маю на увазі, що станеться, якщо я попрошу цей спільний екземпляр і негайно покластися на нього, але dispatch_once займе до Різдва, щоб створити мій об’єкт? Це не повертається відразу так? Принаймні, це, мабуть, уся суть Великої Центральної диспетчеризації.

То чому вони роблять це?


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()над цим полягає в тому, що це швидше. Це також семантично чистіше, тому що він також захищає вас від декількох потоків, роблячи розподілення за допомогою вашої 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 запускаються асинхронно.


Лілі - краща відповідь, але я залишаю свою, щоб зберегти посилання на посаду Майка Еша.
Абізерн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.