Альтернативи dispatch_get_current_queue () для блоків завершення в iOS 6?


101

У мене є метод, який приймає блок і блок завершення. Перший блок повинен працювати у фоновому режимі, тоді як блок завершення повинен працювати в будь-якій черзі, до якої називався метод.

Для останнього я завжди користувався dispatch_get_current_queue(), але здається, що це застаріло в iOS 6 або вище. Що я повинен використовувати замість цього?


чому ви вважаєте, що dispatch_get_current_queue()в iOS 6 застаріле? документи нічого не говорять про це
Jere

3
Укладач скаржиться на це. Спробуй це.
cfischer

4
@jere Перевірте файл заголовка, він говорить про те, що його вилучено
WDUK

Крім обговорень найкращої практики, я бачу [NSOperationQueue currentQueue], який може відповісти на питання. Не впевнений щодо застережень щодо його використання.
Метт

знайдено застереження ------ [NSOperationQueue currentQueue] не те саме, що dispatch_get_current_queue () ----- Іноді він повертає нульове значення ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) є% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) -% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) є <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) є (нульовим) ----- видано або не dispatch_get_current_queue () здається бути єдиним рішенням, яке я бачу для повідомлення про поточну чергу в будь-яких умовах
godzilla

Відповіді:


64

Модель "бігати в будь-яку чергу, на яку викликав абонент" є привабливою, але, зрештою, не чудова ідея. Ця чергу може бути чергою з низьким пріоритетом, основною чергою чи якоюсь іншою чергою з непарними властивостями.

Мій улюблений підхід до цього полягає в тому, щоб сказати, що "блок завершення працює на черзі, визначеній реалізацією, з такими властивостями: x, y, z", і дозволити блоку відправлятися в певну чергу, якщо абонент хоче більше контролю, ніж це. Типовим набором властивостей, які потрібно вказати, було б щось на зразок "послідовне, нереагентне та асинхронічне відносно будь-якої іншої видимої програми" черги ".

** редагувати **

Catfish_Man приклав приклад у коментарях нижче, я просто додаю це до своєї відповіді.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}

7
Повністю узгоджений. Ви можете бачити, що Apple завжди слідує цьому; всякий раз, коли ви хочете щось зробити на головній черзі, вам завжди потрібно відправлятись до основної черги, оскільки яблуко завжди гарантує, що ви перебуваєте в іншому потоці. Більшу частину часу ви чекаєте тривалого запущеного процесу для завершення вилучення / маніпулювання даними, а потім ви можете обробити їх у фоновому режимі прямо у вашому блоці завершення, а потім лише вставити дзвінки інтерфейсу в блок відправки на головній черзі. Крім того, завжди добре дотримуватися того, що Apple задає з точки зору очікувань, оскільки розробники будуть використовуватись до цього шаблону.
Джек Лоуренс

1
чудова відповідь .. але я сподівався на принаймні якийсь зразок коду, щоб проілюструвати те, що ви говорите
abbood

3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) завершенняHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, завершенняHandler);}}
Catfish_Man

(Для цілком тривіального прикладу)
Catfish_Man

3
У загальному випадку це неможливо, оскільки можливо (і насправді досить вірогідно) бути одночасно на більшій черзі, через dispatch_sync () та dispatch_set_target_queue (). Можливі деякі підмножини загального випадку.
Catfish_Man

27

Це принципово неправильний підхід для API, який ви описуєте. Якщо API приймає блок і блок завершення для запуску, такі факти повинні бути правдивими:

  1. "Блок для запуску" повинен запускатися на внутрішній черзі, наприклад, черзі, яка є приватною для API і, таким чином, повністю під контролем цього API. Єдиним винятком з цього є те, якщо API спеціально заявляє, що блок буде виконуватися в основній черзі або в одній з глобальних одночасних черг.

  2. Блок завершення завжди повинен бути виражений у вигляді кортежу (черги, блоку), за винятком тих самих припущень, що і для №1, наприклад, блок завершення буде виконуватися на відомій глобальній черзі. Блок завершення повинен також бути відправлений асинхронізованою по черзі, що передається.

Це не просто стилістичні моменти, вони цілком необхідні, якщо ваш API повинен захищатись від тупиків чи інших поведінкових ситуацій, які інакше колись повісять вас з найближчого дерева. :-)


11
Звучить розумно, але чомусь це не такий підхід, який Apple використовує для власних API: більшість методів, які приймають блок завершення, також не беруть чергу ...
cfischer

2
Щоправда, і дещо змінити моє попереднє твердження, якщо явно очевидно, що блок завершення буде виконуватися в основній черзі або глобальній паралельній черзі. Я зміню свою відповідь, щоб вказати стільки ж.
jkh

Щоб коментувати, що Apple не застосовує такого підходу: Apple не завжди "правильно" на визначення. Власні аргументи завжди сильніші за будь-який конкретний авторитет, що підтвердить будь-який вчений. Я думаю, що відповідь, зазначена вище, дуже добре говорить з точки зору належної архітектури програмного забезпечення.
Вернер Альтевішер

14

Інші відповіді чудові, але для мене відповідь структурна. У мене є такий метод, який є на Singleton:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

яка має дві залежності, які є:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

і

typedef void (^simplest_block)(void); // also could use dispatch_block_t

Таким чином я централізував свої дзвінки про відправку на іншій нитці.


12

Ви повинні бути обережними dispatch_get_current_queueв першу чергу щодо використання. З файлу заголовка:

Рекомендовано лише для налагодження та ведення журналу:

Код не повинен робити жодних припущень щодо повернутої черги, якщо тільки це не є однією з глобальних черг чи чергою, яку сам створив код. Код не повинен припускати, що синхронне виконання на черзі є захищеним від тупикового зв'язку, якщо ці черги не є тією, яку повертає dispatch_get_current_queue ().

Ви можете зробити одну з двох справ:

  1. Зберігайте посилання на чергу, яку ви розмістили спочатку (якщо ви створили її через dispatch_queue_create), і використовуйте її з цього моменту .

  2. Використовуйте визначені системою черги через dispatch_get_global_queueта слідкуйте, яку саме ви використовуєте.

Ефективно, хоча раніше покладаючись на систему для відстеження черги, на якій ви перебуваєте, вам доведеться робити це самостійно.


16
Як ми можемо "зберегти посилання на чергу, про яку ви спочатку розміщували", якщо ми не можемо використати, dispatch_get_current_queue()щоб дізнатися, яка це черга? Іноді код, який повинен знати, на якій черзі він працює, не має ні контролю, ні знань про нього. У мене є багато коду, який можна (і повинен) виконувати у фоновій черзі, але періодично потрібно оновлювати gui (панель прогресу тощо), і тому потрібно dispatch_sync () перейти до основної черги для цих операцій. Якщо вже на головній черзі, dispatch_sync () заблокується назавжди. На це знадобляться місяці, щоб змінити код для цього.
Абхі Беккерт

3
Я думаю, що NSURLConnection видає зворотні виклики завершення в тій самій потоці, з якої і викликається. Чи використовував би той самий API "dispatch_get_current_queue" для збереження черги, з якої вона викликана, щоб використовуватись під час зворотного дзвінка?
defactodeity

5

Apple застаріла dispatch_get_current_queue(), але залишила дірку в іншому місці, тож ми все ще змогли отримати поточну чергу відправки:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

Це працює як мінімум для основної черги. Зауважте, що ця underlyingQueueвластивість доступна з iOS 8.

Якщо вам потрібно виконати блок завершення в початковій черзі, ви також можете використовувати OperationQueueбезпосередньо, я маю на увазі без GCD.



0

Це я теж відповідь. Тому я розповім про наш випадок використання.

У нас є рівень послуг та рівень користувальницького інтерфейсу (серед інших шарів). Службовий рівень виконує завдання у фоновому режимі. (Завдання з обробки даних, завдання CoreData, мережеві дзвінки тощо). Службовий рівень має пару черг роботи для задоволення потреб рівня користувальницького інтерфейсу.

Шар інтерфейсу покладається на рівень послуг, щоб виконати свою роботу, а потім виконати блок завершення успіху. Цей блок може містити код UIKit. Простий варіант використання - отримати всі повідомлення з сервера та перезавантажити представлення колекції.

Тут ми гарантуємо, що блоки, що передаються в рівень послуг, будуть відправлені в чергу, на яку викликується служба. Оскільки dispatch_get_current_queue є застарілим методом, ми використовуємо NSOperationQueue.currentQueue для отримання поточної черги абонента. Важлива примітка щодо цього ресурсу.

Виклик цього методу поза контекстом запущеної операції, як правило, призводить до повернення нуля.

Оскільки ми завжди викликаємо наші сервіси у відомій черзі (Наші користувацькі черги та Основна черга), це добре працює для нас. У нас є випадки, коли serviceA може викликати serviceB, який може викликати serviceC. Оскільки ми контролюємо, звідки здійснюється перший дзвінок служби, ми знаємо, що решта послуг будуть дотримуватися тих же правил.

Тож NSOperationQueue.currentQueue завжди повертатиме одну з наших черг чи MainQueue.

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