上一篇中,我们实现了基本结构。但是如果每次都这么做,会比较麻烦。这一篇中,我们尝试将代码封装起来,这样以后我们再做时,只需调用一次就可以了。
首先,我们将之前的代码变成closure。
import Cocoa
class ViewController: NSViewController {
private let semaphore = DispatchSemaphore(value: 0)
private let concurrentQueue = DispatchQueue.global()
override func viewDidLoad() {
super.viewDidLoad()
concurrentQueue.async {
self.run()
}
}
private func run() {
for i in 1...10 {
print(i)
if shouldBreak(i, withClosure: { (result) in
DispatchQueue.main.async {
self.textView.string += "\(i)\n"
}
if i == 5 {
result = true
}
}) {
break
}
}
}
private func shouldBreak(_ i:Int, withClosure closure: @escaping (inout Bool) -> ()) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
closure(&result)
self.semaphore.signal()
}
semaphore.wait()
return result
}
@IBOutlet var textView: NSTextView!
}
接下来,我们新建一个文件RangeEnumerator.swift
,扩展Sequence
。
import Foundation
extension Sequence {
public func breakableForEach(closureWithCondition: @escaping (Element, inout Bool)->()) {
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
for e in self {
if self.shouldBreak(semaphore, e, withClosure: closureWithCondition) {
break
}
}
}
}
private func shouldBreak(_ semaphore:DispatchSemaphore, _ e:Element, withClosure closure: @escaping (Element, inout Bool) -> ()) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
DispatchQueue.global().asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
closure(e, &result)
semaphore.signal()
}
semaphore.wait()
return result
}
}
之后,最初的代码就可以简化为。
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
(1...10).breakableForEach { (i, result) in
DispatchQueue.main.async {
self.textView.string += "\(i)\n"
}
if i == 5 {
result = true
}
}
}
@IBOutlet var textView: NSTextView!
}
另外,由于扩展中不能创建存储变量,所以,之前的信号量和队列,就必须放在函数里了。