iOS 6中完成块的dispatch_get_current_queue()的替代选择?


101

我有一个接受一个块和一个完成块的方法。第一块应该在后台运行,而完成块应该在调用该方法的任何队列中运行。

对于后者,我一直使用dispatch_get_current_queue(),但似乎在iOS 6或更高版本中已弃用。我应该怎么用呢?


为什么说dispatch_get_current_queue()iOS 6中已弃用?文档对此一无所获
jere 2012年

3
编译器对此抱怨。试试吧。
cfischer 2012年

4
@jere检查头文件,它确实声明它已被
删除

除了关于最佳实践的讨论之外,我看到[NSOperationQueue currentQueue]可能回答了这个问题。不确定有关其用法的注意事项。
马特2014年

发现警告------ [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

Answers:


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始终保证您位于不同的线程上。大多数时候,您都在等待一个长时间运行的过程来完成数据的获取/操作,然后您可以在后台在完成块中对其进行处理,然后仅将UI调用粘贴到主队列上的调度块中。另外,最好遵循Apple设定的期望值,因为开发人员会习惯这种模式。
杰克·劳伦斯

1
很好的答案..但是我希望至少有一些示例代码来说明你在说什么
13年

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

(举一个简单的例子)
Catfish_Man 2013年

3
在一般情况下,这是不可能的,因为由于dispatch_sync()和dispatch_set_target_queue(),有可能(实际上很可能)同时在一个以上队列中。有一些一般情况的子集是可能的。
Catfish_Man 2013年

27

从根本上来说,这是您要描述的API的错误方法。如果API接受一个块和一个完成块来运行,则以下事实必须为真:

  1. “要运行的块”应在内部队列上运行,例如,对API专用的队列,因此完全在该API的控制之下。唯一的例外是,如果API特别声明该块将在主队列或全局并发队列之一上运行。

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

这些不只是风格上的要点,如果您的API要避免死锁或其他极端情况的发生,否则它们将是必不可少的,否则它们将有一天将您从最近的树上吊起来。:-)


11
听起来很合理,但是由于某些原因,这不是Apple为其自己的API所采用的方法:大多数采用完成代码块的方法也不需要排队...
cfischer 2012年

2
是的,如果显然可以在主队列或全局并发队列上运行完成块,则可以对我先前的断言进行一些修改。我将更改答案以表明更多。
2012年

要评论苹果不采用这种方法:苹果并非总是按照定义来“正确”。正确的论点总是比任何科学家都会证实的任何特定权威都要强大。我认为,从正确的软件体系结构角度来看,上面的回答很好地说明了这一点。
Werner Altewischer

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()将永远锁定。为此,我将需要几个月的时间来重构我的代码。
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

这也是我的答案。因此,我将讨论我们的用例。

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

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

在这里,我们保证传递到服务层的块是在调用服务的队列上调度的。由于dispatch_get_current_queue是已弃用的方法,因此我们使用NSOperationQueue.currentQueue来获取调用者的当前队列。此属性的重要说明。

从正在运行的操作的上下文外部调用此方法通常会导致返回nil。

由于我们总是在已知队列(我们的自定义队列和主队列)上调用服务,因此对我们来说效果很好。在某些情况下,serviceA可以调用serviceB,而后者可以调用serviceC。由于我们控制着发出第一个服务调用的位置,因此我们知道其余服务将遵循相同的规则。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.