it-swarm.com.ru

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

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

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

97
cfischer

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

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

** Правка **

Catfish_Man поставил пример в комментариях ниже, я просто добавляю его к своему ответу.

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

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

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

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

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

24
jkh

Другие ответы отличные, но для меня ответ структурный. У меня есть такой метод, который на 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

Таким образом я централизую свои звонки для отправки в другой поток.

14
Dan Rosenstark

Вы должны быть осторожны с использованием dispatch_get_current_queue в первую очередь. Из заголовочного файла:

Рекомендуется только для целей отладки и ведения журнала:

Код не должен делать никаких предположений о возвращаемой очереди, если только это не одна из глобальных очередей или очередь, которую сам код создал. Код не должен предполагать, что синхронное выполнение в очереди защищено от тупиковой ситуации, если эта очередь не та, которую возвращает dispatch_get_current_queue ().

Вы можете сделать одну из двух вещей:

  1. Сохраните ссылку на очередь, в которой вы первоначально разместили (если вы создали ее с помощью dispatch_queue_create), и используйте ее с этого момента.

  2. Используйте системные очереди через dispatch_get_global_queue и отслеживайте, какую из них вы используете.

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

12
WDUK

Для тех, кто все еще нуждается в сравнении очередей, вы можете сравнить очереди по их меткам или спецификациям. Проверьте это https://stackoverflow.com/a/23220741/1531141

4
alexey.hippie

Apple устарела dispatch_get_current_queue(), но оставила дыру в другом месте, поэтому мы все еще можем получить текущую очередь отправки:

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

Это работает для основной очереди по крайней мере. Обратите внимание, что свойство underlyingQueue доступно начиная с iOS 8.

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

4
kelin

Это я тоже ответ. Поэтому я расскажу о нашем случае использования.

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

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

Здесь мы гарантируем, что блоки, которые передаются на уровень служб, отправляются в очередь, в которой была вызвана служба. Поскольку dispatch_get_current_queue является устаревшим методом, мы используем NSOperationQueue.currentQueue для получения текущей очереди вызывающей стороны. Важное замечание об этом свойстве.

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

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

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

0
Kris Subramanian