在同步代码时,循环要提前退出十分简单。
for i in 1...10 {
print(i)
if i == 5 {
break
}
}
但是在异步代码中,要灵活退出就不那么容易了。比如,在调用RestAPI时,如果一切正常,就执行下一次循环,如果出错,则进行提示用户,进行重试或者退出。这个就属于异步操作。
import Cocoa
class ViewController: NSViewController {
private let concurrentQueue = DispatchQueue.global()
override func viewDidLoad() {
super.viewDidLoad()
run()
}
private func run() {
for i in 1...10 {
print(i)
if shouldBreak(i) {
break
}
}
}
private func shouldBreak(_ i:Int) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
if i == 5 {
result = true
}
}
return result
}
}
由于采用了异步,shouldBreak(_ i:Int) -> Bool
的result
是先于DispatchQueue.main.async
中的代码执行的。因此输出始终是1-10
。
为保证执行的顺序,需要使用信号量。
import Cocoa
class ViewController: NSViewController {
private let semaphore = DispatchSemaphore(value: 0)
private let concurrentQueue = DispatchQueue.global()
override func viewDidLoad() {
super.viewDidLoad()
run()
}
private func run() {
for i in 1...10 {
print(i)
if shouldBreak(i) {
break
}
}
}
private func shouldBreak(_ i:Int) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
if i == 5 {
result = true
}
self.semaphore.signal()
}
semaphore.wait()
return result
}
}
代码执行正确。下面我们加入改变UI的部分。添加一个NSTextView
,让它显示每次的i
。
import Cocoa
class ViewController: NSViewController {
private let semaphore = DispatchSemaphore(value: 0)
private let concurrentQueue = DispatchQueue.global()
override func viewDidLoad() {
super.viewDidLoad()
run()
}
private func run() {
for i in 1...10 {
print(i)
if shouldBreak(i) {
break
}
}
}
private func shouldBreak(_ i:Int) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
DispatchQueue.main.async {
self.textView.string += "\(i)\n"
}
if i == 5 {
result = true
}
self.semaphore.signal()
}
semaphore.wait()
return result
}
@IBOutlet var textView: NSTextView!
}
代码执行后,我们会发现,textView
中的i
,不是一行一行显示的,而是一开始不显示,然后一下子都显示出来。这和我们期望的不符。
这是什么原因造成的呢?其实,这是因为视图控制器中的代码,默认运行在图形线程,因此semaphore.wait()
其实每次都阻塞了图形线程。这导致textView
一直没法刷新。直到循环跳出后,界面才成功刷新。
知道了原因,解决办法就有了。将代码从默认的图形线程中移除即可。最终代码:
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) {
break
}
}
}
private func shouldBreak(_ i:Int) -> Bool {
var result = false
let delayInSeconds = Int(arc4random() % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
DispatchQueue.main.async {
self.textView.string += "\(i)\n"
}
if i == 5 {
result = true
}
self.semaphore.signal()
}
semaphore.wait()
return result
}
@IBOutlet var textView: NSTextView!
}