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