ios - 如何在完成 block 之前解决嵌套的异步调用

标签 ios multithreading swift grand-central-dispatch dispatch-async

这是设置。我有一个方法,它有一个完成 block ,我想在其中返回 Item 的列表。这些 Item 是从 API 中获取的。我想让每个提取都异步发生,但最终会一起返回 Item

这是我所拥有的:

public static func fetchItems(numberOfItems: Int, completion: ([Item]?, NSError?) -> ()) -> Void {
    var items: [Item] = []

    let group = dispatch_group_create()

    for (var itemId = 0; itemId < numberOfItems; itemId++) {

        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {

            APIManager.fetchItemWithId(itemId) {
                (item, error) in

                guard let item = item else {
                    // handle error
                }

                print("Item \(itemId) downloaded")

                items.append(item)
            }

        }
    }

    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        completion(items, nil)
    }

}

我的输出结果是:

nil
Item 0 downloaded
Item 1 downloaded
Item 2 downloaded
etc

虽然我异步调度对 Item 的调用,但调用本身在内部有另一个异步操作 - 在示例中由 APIManager.fetchItemWithId 说明。因此,最终,我的完成 在 API 请求解析之前被命中。

我在这里错过了什么?

最佳答案

您的问题在于对 APIManager 的异步调用。您的 block ,在该调用中的 block 之前分派(dispatch)给组完成。实际上,组中的所有 block 在它之前完成。如果您可以选择调用 fetchItemWithId 的同步版本 - 请在此处使用它。如果不是 - 使用 dispatch_semaphore_t

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);     

    APIManager.fetchItemWithId(itemId) {
        (item, error) in
            guard let item = item else {
                // handle error
                dispatch_semaphore_signal(semaphore);
            }

            print("Item \(itemId) downloaded")

            items.append(item)

            dispatch_semaphore_signal(semaphore);
        }

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

如果有什么不清楚的地方,请随时询问。或者,如果我误解了你的意图

更新

我决定添加一些注释以使执行流程清楚为什么一切都按照它的方式发生

 for (var itemId = 0; itemId < numberOfItems; itemId++) {

 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
  //1 reach this point for "numberOfItems" times
        APIManager.fetchItemWithId(itemId) {
            (item, error) in

            guard let item = item else {
                // handle error
            }
             //4 we have no guarantee, when this point will be reached relatively to execution flow of "fetchItems" method. 
             //Actually, looks like it is dispatched to some low priority background queue. 
             //When it is first reached, "group" blocks have already been dispatched and successfully executed
            print("Item \(itemId) downloaded")

            items.append(item)
        }
    //2 previous block has been added to some queue. Reach this point for "numberOfItems" times
    }
  }
  //3 reach this point. Most likely all group blocks have already been executed, so completion block is dispatched almost immediately
  dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    completion(items, nil)
}

关于ios - 如何在完成 block 之前解决嵌套的异步调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32936350/

相关文章:

ios - 运行 ARKit 应用程序时出现错误 "[DeviceMotion] Event ref invalid"

c# - 线程 : Setting checkbox's visibility

ios - drawRect,绘制项目并忽略 mask

objective-c - 导入的 Objc 头文件中 #define 的未解析标识符

ios - UITextView 会缩短文本吗?

ios - Swift - 使用 SpriteKit 提高移动节点的速度

ios - Iphone 开机黑屏

java - 守护线程启动的线程也是守护线程吗?

c# - 锁定写入属性

ios - 更改 mapView Swift 的区域(缩放)