【问题标题】:For loop with closure带闭包的 For 循环
【发布时间】:2021-10-27 01:10:25
【问题描述】:

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

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

【问题讨论】:

  • 这很抽象,可能是XY问题。您能否准确说明您想要实现的目标,而不是如何您想要实现的目标?
  • "只有在之前的完成之后才能匹配每个元素的最佳方式是什么?"是什么让一种方式成为最好的方式?
  • @Alexander 一个例子是在一组图像上使用AVAssetExportSession,并且不想一次导出太多

标签: arrays swift for-loop closures


【解决方案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
    }
}

实现上述目标的等效方法是使用自定义异步Operation 子类(使用定义herehere 的基类AsynchronousOperation),例如

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,您可以利用新的协作线程池来约束并发程度。

有很多不同的模式。

【讨论】:

  • 感谢您的回复!但是,我必须在前一个操作完成后才运行每个操作的原因是因为我使用的 api 需要它(或者它不起作用)
  • 这是看了前两句就知道是谁写的答案之一:)
  • @swifter - 如果您需要它们串行运行,则将最大并发设置为 1。但是您可以使用信号量(在后台队列上)、操作、组合或异步/等待来执行此操作。任君挑选。
  • @Rob 非常感谢您的时间和精力!这非常有效。
【解决方案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)") }
    }
}

【讨论】:

    猜你喜欢
    • 2012-12-15
    • 2015-05-11
    • 2011-01-12
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-30
    • 1970-01-01
    相关资源
    最近更新 更多