Threads
Threadλ μ ννλ thread of executionμ μ€μλ§ μ
λλ€. ν task(μμ
)μ μμμ μμ€ν
μμ μ΄λ»κ² μ€νμν€λμ§λ₯Ό λ»νκ³ μμ£ . μ€μ λ‘ μ°λ¦¬κ° μ¬μ©νλ appμμ μ¬λ¬ μμ
(multiple tasks) μ΄ multiple threadλ‘ λμν©λλ€.
μ΄λ° multithreadingλ‘ μμ
νμ κ²½μ° λ§μ μ₯μ μ΄ μμ΅λλ€.
- Faster execution: concurrentlyνκ² μμ λκΈ° λλ¬Έμ λΉ λ₯΄κ² μμ μ΄ κ°λ₯
- Responsiveness: UIλ main threadμμλ§ μμ λκΈ° λλ¬Έμ, μ¬λ¬ μμ μ νλλΌλ app λ°μμ μν₯μ΄ μλ€.
- Optimized resource consumption: OSμ μ΅μ ν λμ΄ μλ€.
νμ§λ§, μλμ κ°μ 지과 κ°μ΄ μ°λ¦¬κ° μ§μ OSμ threadλ₯Ό μ§μ λ§λ€μ΄μ μ¬μ©νκ² λλ€λ©΄ μ±λ₯μ λ€ μμ’μ μ§λκ±Έ λλ μ μμ΅λλ€.
μ λͺ©: λ΄κ° λ§λ multi thread
Dispatch queues
μμ κ°μ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ DispatchQueueλ₯Ό μ¬μ©ν μ μμ΅λλ€.
DispatchQueueλ μ§μ threadλ₯Ό λ§λλ κ²μ΄ μλ OSμκ² threadλ₯Ό λ§λ€ queueλ₯Ό μ 곡νλ κ²μ
λλ€. κ°λ¨νκ² λ§νλ©΄ OSμ μ§μ threadλ₯Ό λ§λλ κ²μ΄ μλκ³ λ§λ€μ΄μΌν threadλ₯Ό queueμ λ£μ΄ OSμμ threadλ₯Ό μ΅μ νν΄μ λ§λ€ λλ‘ νλ κ²μ
λλ€.
μ½λλ κ°λ¨ν©λλ€.
1
2
let label = "com.grohong.dispatch"
let queue = DispatchQueue(label: label)
μ΄λ κ² DispatchQueueμ κ°λ¨ν λΌλ²¨λ§μΌλ‘ μμ±νλ€λ©΄, νμ ν΄λΉ λΌλ²¨λ‘ queueλ₯Ό μ¬μ¬μ© ν μ μμ΅λλ€.
The main queue
App μ€ννκ² λλ©΄ main dispatch queueκ° λ§λ€μ΄ μ§λλ€. ν΄λΉ queueμ serial queueμμλ UI μμ μ΄ μμ°¨μ μΌλ‘ μΌμ΄λ©λλ€. κ·Έλ¬λ ν΄λΉ queueμ μμ μ λ£μΌλ©΄ μ¬μ©μμ UI λ°μμ μν₯μ΄ κ°κΈ° λλ¬Έμ, μ±λ₯μ΄ μ νλλ κ±Έ 체κ°ν μ μμ΅λλ€.
DispatchQueue.main.sync { } μμ μ νλ©΄ μλλ€!
Quality of service
DispatchQueueλ serial λλ concurrentνκ² μμ±ν μ μμ΅λλ€.
concurrentν DispatchQueueλ κ°λ¨νκ² μμ± ν μ μμ΅λλ€.
1
2
let label = "com.grohong.dispatch.concurrent"
let queue = DispatchQueue(label: label, attributes: .concurrent)
μ΄λ, DispatchQueueμ Quality of service(QoS)λ₯Ό μ€μ νμ¬ μ°μ μμλ₯Ό μ ν μ μμ΅λλ€.
UIμ μκ΄μλ μμ
μ μ°μ μμλ₯Ό μ νμ¬ μ¬μ©νκ³ μΆμκ²½μ° μλμ κ°μ΄ global queueμ QoSλ₯Ό λ§λ€μ΄ λ£μ΄μ£Όλ©΄ λ©λλ€.
Global queuesλ νμ concurrent μ΄κ³ FIFO(first in, firsst out) μ λλ€.
1
let queue = DispatchQueue.global(qos: .userInteractive)
κ°λ³ DispatchQueueλ λ€μκ³Ό κ°μ΄ κ°λ¨νκ² λ§λ€ μ μμ΅λλ€.
1
2
3
let queue = DispatchQueue(label: label,
qos: .userInitiated,
attributes: .concurrent)
QoSλ 6κ°μ§λ‘ λλκ² λ©λλ€.
.userInteractive
userInteractiveλ κ°μ₯ λμ μμλ‘ UI λ°μκ³Ό λμμ λΉ λ₯΄κ² λ°μν μμ
μ λ£μ΄μ€λλ€.
.userInitiated
.userInitiatedλ μ¬μ©μμ λ°μκ³Ό λμμ μμνμ§λ§, λΉλκΈ°μ μΌλ‘ μ²λ¦¬λ μμ
μ λ£μ΄μ€λλ€.
.utility
μμ QoSλ μ±λ₯μ΄ μ€μν μμ
μ΄λΌλ©΄, .utilityλ I/O, networking κ³Ό data feed λ± κΈνμ§ μμ μΌμ μλμ§ ν¨μ¨κ³Ό μ±λ₯μ λμμ μ κ²½ μ°λ μμ
μ λ£μ΄μ€λλ€.
.background
.backgroundλ μ¬μ©μκ° λμΉμ±μ§ λͺ»ν μ λμ μμ
μ λ£μ΄ μ€λλ€.
.default and .unspecified
μ°μ μμλ₯Ό μμ ν΄μ€ default valueμ΄κ³ , unspecified κ±°μ μ¬μ©νμ§ μλ λ¨κ³μ μμ μ λ»ν©λλ€.
Adding task to queues
Dispatch queuesλ taskμ methodλ₯Ό sync λλ async νκ² μ²λ¦¬νλλ‘ μΆκ°ν μ μμ΅λλ€.
κ°λ¨ν async methodλ₯Ό μΆκ°νλ μ½λλ₯Ό λ΄λ³΄κ² μ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self = self else { return }
// Perform your work here
// ...
// Switch back to the main queue to
// update your UI
DispatchQueue.main.async {
self.textLabel.text = "New articles available!"
}
}
μ¬κΈ°μ μ£Όμν μ μ UIμ μ λ°μ΄νΈλ νμ main queueμμ μ΄λ€μ ΈμΌ νλ€λ κ²μ λλ€.
DispatchGroup
DispatchGroupμ classλ‘ κ°κ°μ queueμ tasksλ₯Ό groupμΌλ‘ λ¬Άμ΄ κ΄λ¦¬ν μ μμ΅λλ€.
1
2
3
4
5
6
7
8
9
let group = DispatchGroup()
someQueue.async(group: group) { ... your work ... }
someQueue.async(group: group) { ... more work .... }
someOtherQueue.async(group: group) { ... other work ... }
group.notify(queue: DispatchQueue.main) { [weak self] in
self?.textLabel.text = "All jobs have completed"
}
μμ μ½λμμ λ³Ό μ μλ―μ΄, κ°κ°μ queueμ groupμ μ€μ ν μ μμ΅λλ€.
DispatchGroupsμ notify(queue: )
μ 곡ν©λλ€. ν΄λΉ ν¨μλ groupμ λͺ¨λ taskκ° λͺ¨λ λλ¬μλ μλ €μ€λλ€.
Synchronous waiting
λ§μ½ DispatchGroupμμ taskκ° λλμ§ μμ notify(queue: )
λ₯Ό λ°μ§ λͺ»νλ κ²½μ°κ° μκΈΈ μ μμ΅λλ€.
μ΄λ΄ κ²½μ° wait(timeout)
methodλ₯Ό μ¬μ©νμ¬ .timeOut
μ μ€μ ν μ μμ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async(group: group) {
print("Start job 1")
Thread.sleep(until: Date().addingTimeInterval(10))
print("End job 1")
}
queue.async(group: group) {
print("Start job 2")
Thread.sleep(until: Date().addingTimeInterval(2))
print("End job 2")
}
if group.wait(timeout: .now() + 5) == .timedOut {
print("I got tired of waiting")
} else {
print("All the jobs have completed")
}
μ μ½λλ₯Ό 보면, wait(timeout)
μ 5μ΄μ timeOut
λ₯Ό μ£Όμ΄ λ§μ½ timeout νλΌλ―Έν° μκ°λ΄μ taskκ° λλμ§ μλλ€λ©΄ 쑰건문μμ μμΈμ²λ¦¬κ° κ°λ₯ν©λλ€.
Wrapping asynchronous methods
DispatchGroupμμλ μμ μμ μ²λΌ queueμ taskκ° λλ κ²½μ° groupμ notiλ₯Ό μ€λλ€. νμ§λ§ queueμ task λ΄λΆμ μμ
μ΄ asyncνκ² λλ κ²½μ°μλ taskκ° λ€ λλμ§ μμ μν©μμ notiκ° κ°λ κ²½μ°κ° μκΈΈ κ²μ
λλ€.
μ΄λ΄λ DispatchGroupμ enter μ leaveλ₯Ό μ΄μ©νμ¬ ν΄κ²° ν μ μμ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
queue.dispatch(group: group) {
// count is 1
group.enter()
// count is 2
someAsyncMethod {
defer { group.leave() }
// Perform your work here,
// count goes back to 1 once complete
}
}
μμ μ½λ μ²λΌ taskκ° μμλ λ enter()λ₯Ό νΈμΆνμ¬ DispatchGroupμ λ€μ΄κ°λ κ²μ μλ¦¬κ³ , asyncν taskκ° λλ κ²½μ° deferλ₯Ό μ΄μ©νμ¬ λ§μ§λ§μ taskκ° λ§λ¬΄λ¦¬ λμμ leave() μλ €μ£Όλ©΄ λ©λλ€.
Semaphores
Semaphoreμ μ΄μ©νλ©΄ taskμ μ¬μ©λλ 리μμ€λ₯Ό κ΄λ¦¬ν μ μμ΅λλ€.
μλ μ½λ μ²λΌ DispatchSemaphoreλ₯Ό μμ±ν λ μ¬μ©ν 리μλ₯Ό μ ν μ μμ΅λλ€.
1
let semaphore = DispatchSemaphore(value: 4)
ν΄λΉ DispatchSemaphoreλ waitλ₯Ό μ΄μ©ν΄ 리μμ€λ₯Ό μ€λΉνκ³ , signalλ₯Ό μ΄μ©νμ¬ λ¦¬μμ€ ν΄μ νμμ μ‘°μ ν μ μμ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 1)
let base = "https://wolverine.raywenderlich.com/books/con/image-from-rawpixel-id-"
let ids = [466881, 466910, 466925, 466931, 466978, 467028, 467032, 467042, 467052]
var images: [UIImage] = []
for id in ids {
guard let url = URL(string: "\(base)\(id)-jpeg.jpg") else { continue }
semaphore.wait()
group.enter()
let task = URLSession.shared.dataTask(with: url) { data, _, error in
defer {
print("Finish download \(id)")
group.leave()
semaphore.signal()
}
if error == nil,
let data = data,
let image = UIImage(data: data) {
images.append(image)
}
}
task.resume()
}
// Finish download 466881
// Finish download 466910
// Finish download 466925
// Finish download 466931
// Finish download 466978
// Finish download 467028
// Finish download 467032
// Finish download 467042
// Finish download 467052
μμ μμ λ₯Ό λ³Έλ€λ©΄, DispatchSemaphoreμ 리μμ€κ° νλμ΄κΈ° λλ¬Έμ μ΄λ―Έμ§κ° downloadκ° μμ°¨μ μΌλ‘ μ΄λ€μ§λκ±Έ νμΈ ν μ μμ΅λλ€.
νμ§λ§ 리μμ€λ₯Ό 4κ°λ‘ λλ Έμ κ²½μ° λ¦¬μμ€μ taskκ° λλλ νμ΄λ°μ λ°λΌ λ€μ΄λ‘λκ° λ€μ΄μ΄ μ΄λ£¨μ΄ μ§λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
let semaphore = DispatchSemaphore(value: 4)
// Finish download 466881
// Finish download 466910
// Finish download 466931
// Finish download 467028
// Finish download 467032
// Finish download 467042
// Finish download 466925
// Finish download 466978
// Finish download 467052
// Always change....