iOS 6中用于完成块的dispatch_get_current_queue()的替代方法是什么?

104
我有一个接受块和完成块的方法。第一个块应该在后台运行,而完成块应该在调用方法的任何队列中运行。
至于后者,我之前一直使用dispatch_get_current_queue(),但似乎在iOS 6或更高版本中已被弃用了。那我应该使用什么替代方案呢?

为什么你说dispatch_get_current_queue()在iOS 6中已经被弃用了?文档上并没有提到。 - jere
3
编译器对此抱怨。尝试一下。 - cfischer
4
请检查头文件,它确实声明了已被弃用。 - WDUK
除了关于最佳实践的讨论外,我看到 [NSOperationQueue currentQueue] 可能会回答这个问题。不确定使用时是否有注意事项。 - Matt
发现警告——-- [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) 是 %@", [NSOperationQueue currentQueue]); });

q(0,0) 是 <OS_dispatch_queue_root: com.apple.root.default-qos[0x100195140]> cq(0,0) 是 (null)----- 已弃用或不推荐使用,dispatch_get_current_queue() 看起来是我在所有情况下报告当前队列的唯一解决方案
- godzilla
7个回答

65

“在调用方使用的任何队列上运行”模式看起来很吸引人,但最终并不是一个好主意。该队列可能是低优先级队列、主队列或具有奇怪属性的其他队列。

我喜欢的方法是说“完成块在一个具有这些属性的实现定义队列上运行:x、y、z”,让块分派到特定的队列,如果调用方想要更多的控制权。典型的指定属性集将是类似于“串行、非可重入,并与任何其他应用程序可见队列异步”的东西。

**编辑**

Catfish_Man在下面的评论中放了一个例子,我只是把它添加到他的答案中。

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

7
完全同意。你可以看到苹果一直遵循这个规则;每当你想在主队列上执行某些操作时,你总是需要将其分派到主队列,因为苹果始终保证你在不同的线程上运行。大多数情况下,你都在等待长时间运行的过程完成数据获取/操作,然后你可以在后台的完成块中处理它,仅在主队列上的调度块中添加UI调用。而且,遵循苹果设置的期望通常是一个好习惯,因为开发者会习惯这种模式。 - Jack Lawrence
1
很好的回答..但我希望至少有一些示例代码来说明你所说的。 - abbood
3
  • (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler { dispatch_async(self.workQueue, ^{ [self doSomeWork]; dispatch_async(self.callbackQueue, completionHandler); }); }
这段代码是一个带有完成处理程序的方法。它使用GCD(Grand Central Dispatch)来异步执行一些工作,然后在回调队列上异步执行完成处理程序。
- Catfish_Man
3
由于可以同时在多个队列上,因为dispatch_sync()和dispatch_set_target_queue()函数的使用,所以通常情况下这是不可能的(实际上相当可能)。但是有一些特殊情况是可以的。 - Catfish_Man
我理解其中的吸引力,但真正的解决方案是让AppKit和UIKit断言它们的主线程要求(我已经为两者都提交了radar),而不是让每个库都处理事件。 - Catfish_Man
显示剩余4条评论

27

对于您所描述的API来说,这种方法是基本上错误的。如果一个API接受一个块和一个完成块来运行,那么以下几点需要满足:

  1. "要运行的块"应该在内部队列上运行,例如一个私有于API的队列,因此完全在API的控制之下。唯一例外的情况是如果API明确声明该块将在主队列或其中一个全局并发队列上运行。

  2. 除非与第1点相同的假设成立(例如完成块将在已知的全局队列上运行),否则完成块应始终表示为元组(queue, block)。此外,完成块应异步地在传入的队列上分派。

这些不仅仅是风格上的问题,如果您的API想要避免死锁或其他边缘情况的问题,这些措施是完全必要的,否则某一天您肯定会被吊在最近的树上。 :-)


12
听起来合理,但出于某些原因,苹果公司自己的API并没有采用这种方法:大多数带有完成块的方法并不会同时接受一个队列作为参数... - cfischer
2
真的,为了稍微修改我的先前断言,如果明显可以看出完成块将在主队列或全局并发队列上运行。我会更改我的答案以表明这一点。 - jkh
评论苹果不采取这种方法:苹果并非总是“正确”的定义。适当的论据总是比任何特定的权威更有说服力,这是任何科学家都会确认的。我认为上面的答案从适当的软件架构角度阐述得非常好。 - Werner Altewischer

15

其他答案都很好,但对我来说,答案在于结构。我有一个像这样的方法,它位于单例中:

- (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() 将会永久锁定。重构我的代码需要数月时间。 - Abhi Beckert
3
我认为 NSURLConnection 在同一个调用线程中提供完成回调。它是否会使用相同的 API "dispatch_get_current_queue" 来存储调用来源的队列,并在回调时使用该队列? - defactodeity

5

苹果已经废弃了 dispatch_get_current_queue(),但在另一个地方留下了漏洞,因此我们仍然能够获得当前的调度队列:

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

至少对于主队列来说,这个方法是有效的。需要注意的是,自iOS 8以来,underlyingQueue属性已经可用。

如果您需要在原始队列中执行完成块,则可以直接使用OperationQueue,而无需使用GCD。


4

0

这是一个我也来凑热闹的回答。所以我将谈谈我们的用例。

我们有一个服务层和 UI 层(以及其他层)。服务层在后台运行任务。(数据操作任务,CoreData 任务,网络调用等)服务层有几个操作队列来满足 UI 层的需求。

UI 层依赖于服务层进行工作,然后运行成功完成块。此块可以具有 UIKit 代码。一个简单的用例是从服务器获取所有消息并重新加载集合视图。

在这里,我们保证传递到服务层的块会在调用服务的队列上分派。由于 dispatch_get_current_queue 是一种已弃用的方法,我们使用 NSOperationQueue.currentQueue 来获取调用者的当前队列。关于此属性的重要注释:

从运行中的操作外部调用此方法通常会返回 nil。

由于我们总是在已知队列(我们自定义队列和主队列)上调用我们的服务,因此对我们很有效。我们确实有一些情况,其中 serviceA 可以调用 serviceB,serviceB 可以调用 serviceC。由于我们控制第一个服务调用的位置,我们知道其余服务将遵循相同的规则。

因此,NSOperationQueue.currentQueue将始终返回我们的队列之一或MainQueue。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接