从异步完成处理程序中中断“ for”循环

时间:2019-12-06 11:16:26

标签: swift foreach break completionhandler

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

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

我的代码:

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会给我一条错误消息:

  

未标记的“中断”仅允许在循环或开关(带标记的   要退出if或do,必须先休息一下

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

  

使用未解决的标签“ myLoop”

break self.myLoop也失败了。

print之前添加group.enter()可以证明循环并不仅仅是在完成第一个文件的上传之前完成,而是在“成功上传” /“成功上传”之前打印了文本失败”(按理应如此)。由于这种破坏应该是可能的:

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

2 个答案:

答案 0 :(得分:0)

不使用递归的简单解决方案:添加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!

答案 1 :(得分:0)

我建议的方法基于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)
}

print的{​​{1}}方法中添加finish()行以证明这一点