带闭包的 For 循环

For loop with closure

假设您有一个数组,您想要遍历数组中的每个元素并调用接受该元素作为参数的函数 obj.f

f 是异步的,几乎立即完成,但它会调用 obj.

中的回调处理程序

仅在前一个完成后 匹配每个元素的最佳方法是什么?

这是一种方法:

let arr = ...

var arrayIndex = 0
var obj: SomeObj! // Required

obj = SomeObj(handler: {
    ...
    arrayIndex += 1
    if arrayIndex < arr.count {
        obj.f(arr[arrayIndex])
    }
})
obj.f(arr[0]) // Assumes array has at least 1 element

这很好用,但并不理想。

我可以使用 DispatchSemaphore,但这不是很好,因为它会阻塞当前线程。

此外,每个操作必须 运行 仅当前一个操作完成时的原因是因为我正在使用的 api 需要它(或它中断)

我想知道是否有 better/more 优雅的方法来完成这个?

你说:

Suppose you have an array, and you want to iterate over each element in the array and call a function ... which accepts that element as a parameter.

了解一系列异步任务何时完成的基本 GCD 模式是调度组:

let group = DispatchGroup()

for item in array {
    group.enter()

    someAsynchronousMethod { result in
        // do something with `result`

        group.leave()
    }
}

group.notify(queue: .main) {
    // what to do when everything is done
}

// note, don't use the results here, because the above all runs asynchronously; 
// return your results in the above `notify` block (e.g. perhaps an escaping closure).

如果您想将其限制为最大并发数 4,则可以使用非零信号量模式(但请确保您不从主线程执行此操作),例如

let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)

DispatchQueue.global().async {
    for item in array {
        group.enter()
        semaphore.wait()
    
        someAsynchronousMethod { result in
            // do something with `result`
    
            semaphore.signal()
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        // what to do when everything is done
    }
}

实现上述目标的等效方法是使用自定义异步 Operation subclass(使用基 AsynchronousOperation class 定义的 or here),例如

class BarOperation: AsynchronousOperation {
    private var item: Bar
    private var completion: ((Baz) -> Void)?

    init(item: Bar, completion: @escaping (Baz) -> Void) {
        self.item = item
        self.completion = completion
    }

    override func main() {
        asynchronousProcess(bar) { baz in
            self.completion?(baz)
            self.completion = nil
            self.finish()
        }
    }

    func asynchronousProcess(_ bar: Bar, completion: @escaping (Baz) -> Void) { ... }
}

然后您可以执行以下操作:

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4

let completionOperation = BlockOperation {
    // do something with all the results you gathered
}

for item in array {
    let operation = BarOperation(item: item) { baz in
        // do something with result
    }
    operation.addDependency(completionOperation)
    queue.addOperation(operation)
}

OperationQueue.main.addOperation(completion)

并且使用非零信号量方法和这种操作队列方法,您可以将并发度设置为您想要的任何值(例如 1 = 串行)。

但也有其他模式。例如。 Combine 也提供了实现这一目标的方法 。或者随着 iOS15 macOS 12 中引入的新 async/await,您可以利用新的协作线程池来限制并发度。

有大量不同的模式。

您可以尝试使用 swift async/await,类似于此示例:

struct Xobj {
    func f(_ str: String) async {
        // something that takes time to complete
        Thread.sleep(forTimeInterval: Double.random(in: 1..<3))
    }
}

struct ContentView: View {
    var obj: Xobj = Xobj()
    let arr = ["one", "two", "three", "four", "five"]
    
    var body: some View {
        Text("testing")
            .task {
                await doSequence()
                print("--> all done")
            }
    }
    
    func doSequence() async  {
        for i in arr.indices { await obj.f(arr[i]); print("--> done \(i)") }
    }
}