【问题标题】:Alternatives to dispatch_get_current_queue() for completion blocks in iOS 6?iOS 6 中用于完成块的 dispatch_get_current_queue() 的替代方案?
【发布时间】:2012-11-05 17:39:39
【问题描述】:

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

对于后者,我总是使用dispatch_get_current_queue(),但它似乎在 iOS 6 或更高版本中已被弃用。我应该改用什么?

【问题讨论】:

  • 你为什么说dispatch_get_current_queue()在iOS 6中被弃用了?文档对此只字未提
  • 编译器抱怨它。试试看。
  • @jere 检查头文件,它确实声明它已被贬低
  • 除了讨论什么是最佳实践之外,我看到 [NSOperationQueue currentQueue] 可以回答这个问题。不确定有关其使用的注意事项。
  • 发现警告 ------ [NSOperationQueue currentQueue] 与 dispatch_get_current_queue() 不同 ----- 它有时返回 null ---- dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"q(0,0) is %@", dispatch_get_current_queue()); NSLog(@"cq(0,0) is %@", [NSOperationQueue currentQueue]); }); ----- q(0,0) is cq(0,0) is (null)----- deprecated or not dispatch_get_current_queue() 似乎成为我看到的在所有情况下报告当前队列的唯一解决方案

标签: objective-c cocoa-touch ios6 objective-c-blocks grand-central-dispatch


【解决方案1】:

“在呼叫者所在的任何队列上运行”的模式很吸引人,但最终不是一个好主意。该队列可能是低优先级队列、主队列或其他具有奇怪属性的队列。

我最喜欢的方法是说“完成块在具有以下属性的实现定义的队列上运行:x、y、z”,如果调用者想要更多控制权,则让块分派到特定队列。要指定的一组典型属性类似于“相对于任何其他应用程序可见队列的串行、不可重入和异步”。

** 编辑 **

Catfish_Man 在下面的 cmets 中举了一个例子,我只是将它添加到他的答案中。

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

【讨论】:

  • 完全同意。你可以看到苹果始终遵循这一点;每当你想在主队列上做某事时,你总是需要分派到主队列,因为苹果总是保证你在不同的线程上。大多数时候,您都在等待一个长时间运行的进程完成获取/操作数据,然后您可以在完成块中的后台处理它,然后只将 UI 调用粘贴到主队列上的调度块中。此外,遵循 Apple 设定的期望总是好的,因为开发人员会习惯这种模式。
  • 很好的答案.. 但我希望至少有一些示例代码来说明你在说什么
  • - (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler { dispatch_async(self.workQueue, ^{ [self doSomeWork]; dispatch_async(self.callbackQueue, completionHandler); } }
  • (举个简单的例子)
  • 这在一般情况下是不可能的,因为由于 dispatch_sync() 和 dispatch_set_target_queue() 有可能(实际上很有可能)同时在多个队列中。一般情况的一些子集是可能的。
【解决方案2】:

对于您所描述的 API,这基本上是错误的方法。如果 API 接受块和完成块来运行,则以下事实必须为真:

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

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

这些不仅仅是风格要点,如果您的 API 要避免死锁或其他极端情况行为,否则它们是完全必要的,否则有一天会将您从最近的树上吊死。 :-)

【讨论】:

  • 听起来很合理,但出于某种原因,这不是 Apple 为自己的 API 采用的方法:大多数采用完成块的方法也不采用队列...
  • 是的,并且稍微修改我之前的断言,如果很明显完成块将在主队列或全局并发队列上运行。我将更改我的答案以表明尽可能多。
  • 评论苹果没有采取这种方法:苹果并不总是“正确”的定义。正确的论据总是比任何科学家都会证实的任何特定权威更有力。我认为从适当的软件架构的角度来看,上面的答案很好地说明了这一点。
【解决方案3】:

其他答案很好,但对我来说,答案是结构性的。我有一个像这样的单例方法:

- (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

这样我可以将调用集中在另一个线程上调度。

【讨论】:

    【解决方案4】:

    首先,您应该小心使用dispatch_get_current_queue。从头文件:

    建议仅用于调试和记录目的:

    代码 不得对返回的队列做出任何假设,除非它 是全局队列之一或代码自己创建的队列。 代码不能假设同步执行到队列是 如果该队列不是由返回的队列,则可以避免死锁 dispatch_get_current_queue().

    你可以做以下两件事之一:

    1. 保留对您最初发布的队列的引用(如果您是通过 dispatch_queue_create 创建的),并从那时起使用它。

    2. 通过dispatch_get_global_queue 使用系统定义的队列,并跟踪您正在使用的队列。

    实际上,虽然以前依靠系统来跟踪您所在的队列,但您必须自己做。

    【讨论】:

    • 如果我们不能使用dispatch_get_current_queue() 找出是哪个队列,我们​​如何“保留对您最初发布的队列的引用”?有时,需要知道它在哪个队列上运行的代码对它没有任何控制或知识。我有很多可以(并且应该)在后台队列上执行的代码,但偶尔需要更新 gui(进度条等),因此需要将 dispatch_sync() 转移到这些操作的主队列。如果已经在主队列中,dispatch_sync() 将永远锁定。为此我需要几个月的时间来重构我的代码。
    • 我认为 NSURLConnection 在调用它的同一线程上提供完成回调。是否会使用相同的 API“dispatch_get_current_queue”来存储调用它的队列,以便在回调时使用?
    【解决方案5】:

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

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

    这至少适用于主队列。 请注意,underlyingQueue 属性自 iOS 8 起可用。

    如果你需要在原始队列中执行完成块,你也可以直接使用OperationQueue,我的意思是不用GCD。

    【讨论】:

      【解决方案6】:

      对于那些仍然需要进行队列比较的人,您可以通过他们的标签或指定来比较队列。 检查这个https://stackoverflow.com/a/23220741/1531141

      【讨论】:

        【解决方案7】:

        这也是我的答案。所以我将谈谈我们的用例。

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

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

        这里我们保证传递到服务层的块在调用服务的队列上分派。由于 dispatch_get_current_queue 是一个不推荐使用的方法,我们使用 NSOperationQueue.currentQueue 来获取调用者的当前队列。关于此属性的重要说明。

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

        由于我们总是在已知队列(我们的自定义队列和主队列)上调用我们的服务,这对我们来说效果很好。我们确实有服务A可以调用服务B的情况,而服务B可以调用服务C。由于我们控制了第一个服务调用的来源,我们知道其余的服务将遵循相同的规则。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-14
          • 1970-01-01
          • 2019-12-10
          • 1970-01-01
          • 2014-10-13
          • 2023-01-16
          相关资源
          最近更新 更多