I understood those concepts deeper with a recent project.
Serial and Concurrent Operations
Serial
func serial() {
for i in 0..<5 {
let seconds:UInt32 = (1...3).randomElement()!
sleep(seconds)
print("\tWait \(seconds)s.")
print(i)
}
}
serial()
Wait 3s.
0
Wait 2s.
1
Wait 1s.
2
Wait 2s.
3
Wait 1s.
4
Concurrent
func concurent() {
let queue = DispatchQueue.global()
for i in 0..<5 {
queue.async {
let seconds:UInt32 = (1...3).randomElement()!
sleep(seconds)
print("\tWait \(seconds)s.")
print(i)
}
}
}
concurent()
Wait 2s.
2
Wait 3s.
0
Wait 3s.
Wait 3s.
Wait 3s.
4
3
1
We can see that with serial operations, the codes are running one by one. And with concurrent operations, the codes are running in parallels.
Closure and async/await
Closure
func closure() {
let url = URL(string: "https://zhaoxin.pro")!
let task = URLSession.shared.dataTask(with: url) { data, urlResponse, error in
if let error {
print(error)
return
}
if let httpResponse = urlResponse as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data, let output = String(data: data, encoding: .utf8) {
print(output)
}
}
task.resume()
}
closure()
<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js"><!--<![endif]-->
<head>
<meta charset="utf-8">
<title>
肇鑫的技术博客
</title>
<meta name="author" content="">
<meta name="description" content="业精于勤,荒于嬉">
...
async/await
func async_await() async throws {
let url = URL(string: "https://zhaoxin.pro")!
let (data, urlResponse) = try await URLSession.shared.data(from: url)
if let httpResponse = urlResponse as? HTTPURLResponse,
httpResponse.statusCode == 200,
let output = String(data: data, encoding: .utf8) {
print(output)
}
}
do {
try await async_await()
} catch let error {
print(error)
}
As you can see, most of the closures can convert to async/await, which makes them easy to understand. However, not all closures could be converted as async/await. For example, there are many API in Photos could not be converted to async/await. As those closures are called more than one time.
Dispatch Semaphore and Dispatch Group
For concurrent operations, if we want to do something after all operations are finished. We can use a timer, a Dispatch Semaphore, or a Dispatch Group.
Timer
func concurrentWithTimer() {
let queue = DispatchQueue.global()
let total = 5
var finished = 0
let lock = NSRecursiveLock()
for i in 0..<5 {
queue.async {
let seconds:UInt32 = (1...3).randomElement()!
sleep(seconds)
print("\tWait \(seconds)s.")
print(i)
lock.lock()
finished += 1
lock.unlock()
}
}
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { timer in
if finished == total {
timer.invalidate()
print("All Finished")
}
})
}
concurrentWithTimer()
Wait 1s.
Wait 1s.
0
2
Wait 2s.
3
Wait 3s.
Wait 3s.
4
1
All Finished
Using Timer is easy to understand, but it costs more as it runs many times.
Dispatch Semaphore
func dispatch_semaphore() {
let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore(value: 0)
let total = 5
var finished = 0
let lock = NSRecursiveLock()
for i in 0..<5 {
queue.async {
let seconds:UInt32 = (1...3).randomElement()!
sleep(seconds)
print("\tWait \(seconds)s.")
print(i)
lock.lock()
finished += 1
if finished == total {
semaphore.signal()
}
lock.unlock()
}
}
semaphore.wait()
print("All Finished")
}
Semaphore only runs one time. But you need to deal with extra local variables, even more, you have to use lock to void data racing.
Dispatch Group
func dispatch_group() {
let queue = DispatchQueue.global()
let group = DispatchGroup()
for i in 0..<5 {
group.enter()
queue.async {
let seconds:UInt32 = (1...3).randomElement()!
sleep(seconds)
print("\tWait \(seconds)s.")
print(i)
group.leave()
}
}
group.wait()
print("All Finished")
}
Dispatch Group is the most elegant way to do the same job. There is no extra variables and no more extra operations. Just enter and leave then wait. Everything works like a charm.