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

How to get nested asynchronous calls resolved before completion block

这是设置。我有一个方法,它有一个完成块,我想在其中 return 一个 Item 的列表。这些 Item 是从 API 中提取的。我想让每个提取异步发生,但最终 return 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 说明。因此,最终,我的 completion 在 API 请求解决之前被命中。

我在这里错过了什么?

您的问题在于对 APIManager 的异步调用。您的块,在该调用中的块之前分派给组完成。实际上,组中的 all 个块在它之前完成。如果您可以选择调用 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)
}