如何在Swift 4中同时进行两个API调用

18

在此感谢您的帮助, 我有两个API调用,两者都是并行的,任何一个调用都可能首先成功(我不想按顺序调用)。 在两个调用成功后,我必须停止我的活动指示器并重新加载我的tableView, 这是我的代码,但我不知道这是否是正确的方法以及如何重新加载我的tableView和停止我的活动指示器。

func downloadDetails(){
    let operationQueue: OperationQueue = OperationQueue()
    let operation1 = BlockOperation() {
    WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in

          if let success = data {
              DispatchQueue.main.async {
                  (success code)
              }
           }
        })

        let operation2 = BlockOperation() {
        webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in

                if let success = data {
                    DispatchQueue.main.async {
                       (success code)
                    }
                }
            })
        }
        operationQueue.addOperation(operation2)
    }
    operationQueue.addOperation(operation1)
}
downloadDetails() "calling function"
3个回答

47

这正是使用DispatchGroup的典型场景。每次调用时加入分组,在调用结束时离开分组,并添加通知处理程序以在所有操作完成时触发。不需要单独的操作队列;这些已经是异步操作。

func downloadDetails(){
    let dispatchGroup = DispatchGroup()

    dispatchGroup.enter()   // <<---
    WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in

        if let success = data {

            DispatchQueue.main.async {
                (success code)
                dispatchGroup.leave()   // <<----
            }
        }
    })

    dispatchGroup.enter()   // <<---
    webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in

        if let success = data {

            DispatchQueue.main.async {
               (success code)
               dispatchGroup.leave()   // <<----
            }
        }
    })

    dispatchGroup.notify(queue: .main) {
        // whatever you want to do when both are done
    }
}

如果调用失败了怎么办?我能在错误块中使用dispatchGroup.leave()吗? - King
2
是的。无论成功与否,您都必须为每个“enter()”调用一次“leave()”。 - Rob Napier
1
以你的意思来说,答案是肯定的,但这不是一个好的思考方式。每次进入都必须恰好离开一次。你可以完全取消操作;但你仍然需要离开该组。(我知道这很迂腐;但从它们真正做的事情的角度来看,思考组是很重要的。)如果你的意思是不能避免运行通知块,那是正确的。没有办法取消订阅组完成。如果你想要这种类型的取消,你可以向上移动一层到操作,但我发现它们对于大多数问题来说过于复杂。 - Rob Napier
明白了。非常感谢 @RobNapier。 - k-thorat
嗨@RobNapier,我想将4个API组合在一起,我使用了DispatchGroup enter()leave()但是没有任何变化。这些API需要等待另一个API完成,它们不是并发的。 - Farras Doko
显示剩余5条评论

1

我会使用OperationQueue。

它适用于长时间运行的任务,并且如果需要,可以让您控制取消请求。

在每个操作结束时,您可以检查操作计数以了解剩余操作。

我已添加伪代码。

let operationQueue: OperationQueue = OperationQueue()

func downloadDetails(){

    let operation1 = BlockOperation() { [weak self] in

        guard let strongSelf = self else {
            return
        }

        sleep(2)

        DispatchQueue.main.async {
            strongSelf.handleResponse()
        }

        let operation2 = BlockOperation() { [weak self] in

            guard let strongSelf = self else {
                return
            }

            sleep(2)

            DispatchQueue.main.async {
                strongSelf.handleResponse()
            }
        }
        strongSelf.operationQueue.addOperation(operation2)
    }

    self.operationQueue.addOperation(operation1)
}

func handleResponse() {
    print("OPERATIONS IN PROGRESS: \(self.operationQueue.operations.count)")
    if self.operationQueue.operations.count == 0 {
        print("ALL OPERATIONS ARE COMPLETE")
    }
}

func cancelOperation() {
    self.operationQueue.cancelAllOperations()
}

这将打印

OPERATIONS IN PROGRESS: 1
OPERATIONS IN PROGRESS: 0
ALL OPERATIONS ARE COMPLETE

非常感谢kthorat的快速回复。但我想知道如何在这里触发cancelOperation()? - King
如果您不再需要请求,应该取消它。例如,如果您在屏幕上开始请求,则应在用户离开屏幕后取消请求。因此,如果ViewWillAppear正在进行请求,那么您可以在ViewWillDisappear上取消请求。有道理吗? - k-thorat
@k-thorat 如果我想按顺序调用两个或更多的API怎么办? - Saurabh

0
调度组在某种情况下可能会失败,假设第一个 API 响应在进入第二个组之前返回。因此,在这种情况下,您的通知块将在没有第二个 API 的情况下被调用。为了防止这个问题,您必须在任何 API 调用之前添加所有的进入语句。例如 -
func download(){
    let dispatchGroup = DispatchGroup()
    /// Enter into the group for all the apis from here only.
    dispatchGroup.enter()  
    dispatchGroup.enter()   
    
    ApiManager.shared.data(request: firstRequest, withCompletion: {(data: Any? , error: Error?) -> Void in
        dispatchGroup.leave()  
    })

    ApiManager.shared.data(request: secondRequest, withCompletion: {(data: Any? , error: Error?) -> Void in
        dispatchGroup.leave() 
    })

    dispatchGroup.notify(queue: .main) {
        /// From here you may notify to hide indicator and reload the table.
    }
}

这不是问题。即使第二个enter放在第二个API调用之前,它也会在第一个API调用的完成处理程序执行之前被执行。 - vadian

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