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

101

У меня есть метод, который принимает блок и блок завершения. Первый блок должен работать в фоновом режиме, а блок завершения должен выполняться в той очереди, в которой был вызван метод.

Последнее я всегда использовал dispatch_get_current_queue(), но похоже, что он устарел в iOS 6 или выше. Что мне использовать вместо этого?

cfischer
источник
почему вы говорите, что dispatch_get_current_queue()это устарело в iOS 6? в документах ничего об этом не сказано
jere 05
3
Компилятор жалуется на это. Попытайся.
cfischer 05
4
@jere Проверьте заголовочный файл, там действительно указано, что он лишен
WDUK
Помимо обсуждений наилучших методов, я вижу [NSOperationQueue currentQueue], который может ответить на этот вопрос. Не уверен в предостережениях относительно его использования.
Мэтт
обнаружено предупреждение ------ [NSOperationQueue currentQueue] не то же самое, что dispatch_get_current_queue () ----- Иногда возвращает null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) это% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) is% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) is <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) is (null) ----- деприминировано или нет dispatch_get_current_queue () кажется быть единственным решением, которое я вижу для сообщения о текущей очереди при любых условиях
Годзилла

Ответы:

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 всегда гарантирует, что вы находитесь в другом потоке. Большую часть времени вы ждете, пока длительный процесс завершит выборку / обработку данных, а затем вы можете обработать его в фоновом режиме прямо в своем блоке завершения, а затем только вставлять вызовы пользовательского интерфейса в блок отправки в основной очереди. Кроме того, всегда хорошо следовать тому, что Apple устанавливает в отношении ожиданий, поскольку разработчики привыкли к этому шаблону.
Джек Лоуренс
1
отличный ответ .. но я надеялся получить хоть какой-то образец кода, чтобы проиллюстрировать то, что вы говорите
abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) completedHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completingHandler);}}
Catfish_Man
(Для совершенно тривиального примера)
Catfish_Man
3
В общем случае это невозможно, потому что возможно (и на самом деле весьма вероятно) находиться в более чем одной очереди одновременно из-за dispatch_sync () и dispatch_set_target_queue (). Возможны некоторые подмножества общего случая.
Catfish_Man
27

Это принципиально неправильный подход к API, который вы описываете. Если API принимает блок и блок завершения для запуска, должны быть верными следующие факты:

  1. «Блок для запуска» должен запускаться во внутренней очереди, например, в очереди, которая является частной для API и, следовательно, полностью находится под контролем этого API. Единственное исключение из этого - если API специально объявляет, что блок будет запускаться в основной очереди или одной из глобальных параллельных очередей.

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

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

jkh
источник
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и отслеживайте, какую из них вы используете.

Фактически, хотя раньше вы полагались на систему, чтобы отслеживать очередь, в которой вы находитесь, вам придется делать это самостоятельно.

WDUK
источник
16
Как мы можем «сохранить ссылку на очередь, в которой вы изначально разместили», если мы не можем использовать ее, dispatch_get_current_queue()чтобы узнать, какая это очередь? Иногда код, которому нужно знать, в какой очереди он запущен, не имеет никакого контроля или информации о нем. У меня есть много кода, который может (и должен) выполняться в фоновой очереди, но иногда мне нужно обновлять графический интерфейс (индикатор выполнения и т. Д.), И поэтому для этих операций необходимо использовать dispatch_sync () в основной очереди. Если он уже находится в основной очереди, dispatch_sync () заблокируется навсегда. На рефакторинг кода для этого у меня уйдут месяцы.
Abhi Beckert
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, чтобы получить текущую очередь вызывающего. Важное примечание об этой собственности.

Вызов этого метода извне контекста выполняющейся операции обычно приводит к возврату nil.

Поскольку мы всегда вызываем наши службы в известной очереди (наши пользовательские очереди и основная очередь), это хорошо для нас работает. У нас действительно есть случаи, когда serviceA может вызвать serviceB, который может вызвать serviceC. Поскольку мы контролируем, откуда происходит первый вызов службы, мы знаем, что остальные службы будут следовать тем же правилам.

Таким образом, NSOperationQueue.currentQueue всегда будет возвращать одну из наших очередей или MainQueue.

Крис Субраманиан
источник