【问题标题】:Break "for" loop from within async completion handler从异步完成处理程序中中断“for”循环
【发布时间】:2019-12-06 11:16:26
【问题描述】:

我的应用程序 (Swift 5) 使用 for 循环和 i.a. 中的异步完成处理程序将文件发送到服务器。一个信号量,以确保同时只发送一个文件。

如果上传失败或出现异常,我想打破循环以显示错误消息。

我的代码:

let group = DispatchGroup()
let queue = DispatchQueue(label: "someLabel")
let sema = DispatchSemaphore(value: 0)

queue.async {
    for (i,item) in myArray.enumerated() {
        group.enter()

        do {
            let data = try Data(contentsOf: item.url)

            ftpProvider.uploadData(folder: "", filename: item.filename, data: data, multipleFiles: true, completion: { (success, error) in
                if success {
                    print("Upload successful!")
                } else {
                    print("Upload failed!")
                    //TODO: Break here!
                }
            group.leave()
            sema.signal()
        })
        sema.wait()
        } catch {
            print("Error: \(error.localizedDescription)")
            //TODO: Break here!
        }
    }
}

group.notify(queue: queue) {
    DispatchQueue.main.async {
        print("Done!")
    }
}

添加break 会给我一条错误消息:

无标签的'break'只允许在循环或开关内,一个有标签的 退出 if 或 do 需要 break

向循环添加标签 (myLoop: for (i,s) in myArray.enumerated()) 也不起作用:

使用未解决的标签“myLoop”

break self.myLoop 也失败了。

group.enter() 之前添加print 证明循环不是在第一个文件上传完成之前完成,而是在“上传成功”/“上传失败”之前打印文本(正如它应该的那样)。因为这种破坏应该是可能的:

如何中断循环,以便在group.notify 中显示错误对话框?

【问题讨论】:

  • 可以在这里找到类似的问题:reddit.com/r/swift/comments/bih0zn/…
  • 使用异步操作和串行操作队列,而不是丑陋的信号量(在您的情况下该组毫无意义)。好处是如果无法上传文件,您可以取消所有操作。
  • @swiftlynx 您链接到的代码也使用递归(如 Sh_Khan 的回答),这意味着重组整个事情,因为它比我在问题中显示的要多。我宁愿跳出循环。
  • @vadian 在第一个文件上传之前,很快就摆脱了该组,group.notify(queue: queue) 也立即调用了print("Done!")。代码已经在后台线程中运行。我要调查OperationQueues,好的。同时:是否有可能用我当前的代码跳出循环?
  • @Neph 不,不是。

标签: swift foreach break completionhandler


【解决方案1】:

不使用递归的简单解决方案:添加Bool 以检查循环是否应该中断,然后在完成处理程序之外将其中断:

let group = DispatchGroup()
let queue = DispatchQueue(label: "someLabel")
let sema = DispatchSemaphore(value: 0)

queue.async {
    var everythingOkay:Bool = true

    for (i,item) in myArray.enumerated() {
        //print("Loop iteration: \(i)")

        if everythingOkay {
            group.enter()

            do {
                let data = try Data(contentsOf: item.url)

                ftpProvider.uploadData(folder: "", filename: item.filename, data: data, multipleFiles: true, completion: { (success, error) in
                    if success {
                        print("Upload successful!")
                        everythingOkay = true
                    } else {
                        print("Upload failed!")
                        everythingOkay = false
                    }
                group.leave()
                sema.signal()
            })
            sema.wait()
            } catch {
                print("Error: \(error.localizedDescription)")
                everythingOkay = false
            }
        } else {
            break
        }
    }
}

group.notify(queue: queue) {
    DispatchQueue.main.async {
        print("Done!")
    }
}

通常使用这样的Bool 是行不通的,因为循环会在第一个文件上传之前完成。

这就是DispatchGroupDispatchSemaphore 发挥作用的地方:它们确保下一个循环迭代在前一个循环完成之前不会开始,这意味着文件将按照它们的顺序上传在myArray 中列出(建议使用此方法here)。

这可以用上面代码中的打印进行测试,然后在“上传成功!”/“上传失败!”之前打印出来。每次迭代,例如:

Loop iteration: 0
Upload successful
Loop iteration: 1
Upload successful
Loop iteration: 2
Upload failed
Done!

【讨论】:

    【解决方案2】:

    我建议的方法基于this question 接受的答案中提供的AsynchronousOperation

    创建类,复制代码并创建AsynchronousOperation 的子类,包括您的异步任务和完成处理程序

    class FTPOperation: AsynchronousOperation {
    
        var completion : ((Result<Bool,Error>) -> Void)?
        let item : Item // replace Item with your custom class
    
        init(item : Item) {
            self.item = item
        }
    
        override func main() {
            do {
                let data = try Data(contentsOf: item.url)
                ftpProvider.uploadData(folder: "", filename: item.filename, data: data, multipleFiles: true) { (success, error) in
                    if success {
                        completion?(.success(true))
                    } else {
                        completion?(.failure(error))
                    }
                    self.finish()
                }
            } catch {
                completion?(.failure(error))
                self.finish()
            }
        }
    }
    

    在控制器中添加串行操作队列

    let operationQueue : OperationQueue = {
        let queue = OperationQueue()
        queue.name = "FTPQueue"
        queue.maxConcurrentOperationCount = 1
        return queue
    }()
    

    并运行操作。如果返回错误,则取消所有挂起的操作

    for item in myArray {
        let operation = FTPOperation(item: item)
        operation.completion = { result in
            switch result {
                case .success(_) : print("OK", item.filename)
                case .failure(let error) :
                   print(error)
                   self.operationQueue.cancelAllOperations()
            }
        }
        operationQueue.addOperation(operation)
    }
    

    AsynchronousOperationfinish()方法中加一行print来证明

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-07
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-25
      相关资源
      最近更新 更多