带闭合的 For 环

For loop with closure

提问人:swifter 提问时间:8/27/2021 最后编辑:swifter 更新时间:8/28/2021 访问量:368

问:

假设您有一个数组,并且想要遍历数组中的每个元素并调用一个接受该元素作为参数的函数。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 需要它(或者它中断了)

我想知道是否有更好/更优雅的方法来实现这一目标?

数组 Swift for 循环 闭包

评论

0赞 Alexander 8/27/2021
这很抽象,可能是一个XY问题。您能否准确解释一下您想要实现的目标,而不是您如何实现它?
0赞 El Tomato 8/27/2021
“只有在前一个完成之后,才匹配每个元素的最佳方法是什么?”是什么让 One Way 成为最好的?
0赞 swifter 8/27/2021
@Alexander 例如,可以在一组图像上使用,并且不想一次导出太多图像AVAssetExportSession

答:

1赞 Rob 8/27/2021 #1

你说:

假设你有一个数组,你想遍历数组中的每个元素并调用一个函数......它接受该元素作为参数。

当一系列异步任务完成时,要知道的基本 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
    }
}

实现上述目标的等效方法是使用自定义异步子类(使用此处或此处定义的基类),例如OperationAsynchronousOperation

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 也提供了实现这一目标的方法,https://stackoverflow.com/a/66628970/1271826。或者,借助 iOS 15 和 macOS 12 中引入的新 async/await,您可以利用新的协作线程池来限制并发程度。

有很多不同的模式。

评论

0赞 swifter 8/27/2021
感谢您的回复!但是,我必须仅在前一个操作完成后才运行每个操作的原因是因为我使用的 api 需要它(或者它不起作用)
2赞 Martin R 8/27/2021
这是阅读前两句话后知道是谁写的答案之一:)
0赞 Rob 8/28/2021
@swifter - 如果需要它们串行运行,则将最大并发设置为 1。但是您可以使用信号量(在后台队列上)、操作、组合或异步/等待来执行此操作。任您挑选。
0赞 swifter 8/28/2021
@Rob 非常感谢您的时间和精力!这非常有效。
0赞 workingdog support Ukraine 8/27/2021 #2

您可以尝试使用 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)") }
    }
}