根据 DispatchQueue 上的先前结果添加条件

Adding condition based on previous result on DispatchQueue

是否可以在DispatchQueue的下一个队列上设置条件?假设有 2 个 API 调用应该同步执行,callAPI1 -> callAPI2。但是,callAPI2 应该只在 callAPI1 返回 true 时执行。请检查下面的代码以了解更清楚的情况:

    let dispatchQueue: DispatchQueue = DispatchQueue(label: "queue")
    let dispatchGroup = DispatchGroup()

    var isSuccess: Bool = false


    dispatchGroup.enter()
    dispatchQueue.sync {
        self.callAPI1(completion: { (result) in
            isSuccess = result
            dispatchGroup.leave()
        }
    }

    dispatchGroup.enter()
    dispatchQueue.sync {
        if isSuccess { //--> This one always get false
            self.callAPI2(completion: { (result) in
                isSuccess = result
                dispatchGroup.leave()
            })
        } else {
            dispatchGroup.leave()
        }
    }

    dispatchGroup.notify(queue: DispatchQueue.main, execute: {
        completion(isSuccess) //--> This one always get false
    })

目前上面的代码总是返回 isSuccess 作为 false 尽管 callAPI1 的调用返回 true,这导致只有 callAPI1 被调用。

所有非 playground 代码直接输入到答案中,预计会有少量错误。

看来您正在尝试将异步调用转换为同步调用,而您尝试此操作的方式根本行不通。假设 callAPI1 是异步的,那么之后:

self.callAPI1(completion: { (result) in
   isSuccess = result
}

完成块(很可能)还没有 运行,你不能立即测试 isSuccess,如:

self.callAPI1(completion: { (result) in
   isSuccess = result
}
if isSuccess
{
   // in all probability this will never be reached
}

将代码包装到同步块中将没有任何效果:

dispatchQueue.sync
{
   self.callAPI1(completion: { (result) in
      isSuccess = result
   }
   // at this point, in all probability, the completion block
   // has not yet run, therefore...
}
// at this point it has also not run

同步调度只是 运行 将它的块放在不同的队列中并 等待 完成;如果那个块像你的那样包含异步代码,那么它就不会神奇地变成同步的——它像往常一样异步执行,同步分派的块终止,同步分派 returns,然后你的代码继续。同步分派没有实际效果(除了 运行 将块放在不同的队列上同时阻塞当前队列)。

如果您需要对多个异步调用进行排序,您可以通过多种方式完成。一种方法是通过完成块简单地链接调用。使用这种方法,您的代码将变为:

self.callAPI1(completion: { (result) in
   if !result { completion(false) }
   else
   {
      self.callAPI2(completion: { (result) in
         completion(result)
      }
   }
}

使用信号量

如果使用上述模式有很长的此类调用序列,那么代码可能会变得非常嵌套,在这种情况下,您可以使用 semaphores 来排序电话。一个简单的信号量可以用来阻塞(线程)执行,使用wait(),直到它被(由未阻塞的线程)发出信号 , 使用 signal().

注意这里强调的是阻塞,一旦引入了阻塞执行的能力,就必须考虑各种问题:其中包括UI 响应能力 - 阻塞UI 线程不好;死锁 - 例如,如果将发出信号量等待和信号操作的代码在同一个线程上执行,那么在等待之后将没有信号......

这是一个示例 Swift Playground 脚本,用于演示如何使用信号量。该模式遵循您的原始代码,但除了您的布尔值之外还使用了信号量。

import Cocoa

// some convenience functions for our dummy callAPI1 & callAPI2
func random(_ range : CountableClosedRange<UInt32>) -> UInt32
{
    let lower = range.lowerBound
    let upper = range.upperBound
    return lower + arc4random_uniform(upper - lower + 1)
}

func randomBool() -> Bool
{
    return random(0...1) == 1
}

class Demo
{
    // grab the global concurrent utility queue to schedule our work on
    let workerQueue = DispatchQueue.global(qos : .utility)

    // dummy callAPI1, just pauses and then randomly return success or failure
    func callAPI1(_ completion : @escaping (Bool) -> Void) -> Void
    {
        // do the "work" on workerQueue, which is concurrent so other work
        // can be executing, or *blocked*, on the same queue
        let pause = random(1...2)
        workerQueue.asyncAfter(deadline: .now() +  Double(pause))
        {
            // produce a random success result
            let success = randomBool()
            print("callAPI1 after \(pause) -> \(success)")
            completion(success)
        }
    }

    func callAPI2(_ completion : @escaping (Bool) -> Void) -> Void
    {
        let pause = random(1...2)
        workerQueue.asyncAfter(deadline: .now() +  Double(pause))
        {
            let success = randomBool()
            print("callAPI2 after \(pause) -> \(success)")
            completion(success)
        }
    }

    func runDemo(_ completion : @escaping (Bool) -> Void) -> Void
    {
        // We run the demo as a standard async function
        // which doesn't block the main thread
        workerQueue.async
        {
            print("Demo starting...")
            var isSuccess: Bool = false
            let semaphore = DispatchSemaphore(value: 0)

            // do the first call
            // this will asynchronously execute on a different thread
            // *including* its completion block
            self.callAPI1
            { (result) in
                isSuccess = result
                semaphore.signal() // signal completion
            }

            // we can safely wait for the semaphore to be
            // signalled as callAPI1 is executing on a different
            // thread so we will not deadlock
            semaphore.wait()

            if isSuccess
            {
                self.callAPI2
                { (result) in
                    isSuccess = result
                    semaphore.signal() // signal completion
                }
                semaphore.wait() // wait for completion
            }
            completion(isSuccess)
        }
    }
}

Demo().runDemo { (result) in print("Demo result: \(result)") }

// For the Playground
// ==================
// The Playground can terminate a program run once the main thread is done
// and before all async work is finished. This can result in incomplete execution
// and/or errors. To avoid this we sleep the main thread for a few seconds.
sleep(6)
print("All done")

// Run the Playground multiple times, the results should vary
// (different wait times, callAPI2 may not run). Wait until
// the "All done"" before starting next run
// (i.e. don't push stop, it confuses the Playground)

或者...

另一种避免嵌套的方法是设计采用两个异步方法并通过实现嵌套模式生成单个异步方法的函数(或运算符)。然后可以将长嵌套序列减少为更线性的序列。这种方法留作练习。

HTH