Swift의 동시성
동시성은 여러 작업을 병렬로 수행하는 소프트웨어 기능으로 정의할 수 있다 스위프트 언어에서 구조화된 동시성 기능을 살펴보고 이 기능을 이용해서 앱 프로젝트에 멀티태스킹을 지원할 수 있다
애플리케이션 메인 스레드
앱이 처음 시작될 때 런타임 시스템은 보통 앱이 기본적으로 실행되는 단일 스레드(메인 스레드)를 생성한다 메인 스레드를 사용해서 시간이 걸리는 작업을 수행하면 화면이 멈춘것처럼 보인다 이 작업을 다른 스레드에서 시작하면 메인 스레드를 계속 사용할 수 있다
구조화된 동시성
구조화된 동시성의 이름이 뭔가 어렵게 느껴지지만 비동기 작업을 더 쉽게 안전하게, 관리 가능하게 처리할 수 있도록 도와주는 개념이다 이 개념은 비동기 작업을 어떻게 시작하고 실행하고 완료할지 명확히 제어할 수 있게 해준다 즉 백그라운드 작업이 언제 종료되는지 어떻게 종료되는지에 대해 걱정하지 않도록 도와준다 (스위프트에서 비동기 함수를 작성하는 방법이라고 생각하면 될것 같다)
동기 코드
동기 코드는 비동기 코드의 반대로 코드를 직접 실행해서 확인해본다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
doSomething()
}){
Text("do something")
}
}
func doSomething() {
print("Start \(Date())")
tasksTooLong()
print("End \(Date())")
}
func tasksTooLong() {
sleep(5)
print("complete task at \(Date())")
}
}
종료 시간이 5초 이류 이다 tasksTooLong이 지속되는 5초간에 이후 함수들이 호출되지 않는다 사용자에게는 5초간 화면이 멈춘것 처럼 보인다
async/await
구조화된 동시성의 기초는 async/await쌍이다 async 키워드는 함수가 호출된 스레드에 대해 비동기적으로 실행됨을 나타내기 위해 함수를 선언할 때 사용한다 (호출된 함수가 다른 스레드에서 실행) 이렇게 선언된 함수들은 await 키워드를 통해서만 호출될 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
Task{
await doSomething()
}
}){
Text("do something")
}
}
func doSomething() async {
print("Start \(Date())")
await tasksTooLong()
print("End \(Date())")
}
func tasksTooLong() async {
sleep(5)
print("complete task at \(Date())")
}
}
이번 결과는 이전 결과와 똑같이 5초뒤에 끝이 났다 doSomethong함수가 계속 진행되지 못하고 tasksTooLong함수가 반환될 때 까지 기다려야하기 때문에 작업을 호출한 스레드를 여전히 차단하고 있다는 느낌을 준다 (부모 비동기 함수는 모든 하위 함수가 완료될 때까지 완료할 수 없기 때문이다)
async-let을 사용한다면 부모 함수에서 기다리는 것을 나중으로 미룰 수 있다
async-let 바인딩 사용
비동기 함수가 백그라운드에서 실행되는 동안 호출하는 함수 내에서 코드를 계속 실행한다
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
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
Task{
await doSomething()
}
}){
Text("do something")
}
}
func doSomething() async {
print("Start \(Date())")
async let result = takesTooLong()
print("after async let use time :\(Date())")
// 여기에는 비동기 함수와 동시에 동작하는 코드
print("result : \(await result)")
print("End \(Date())")
}
func takesTooLong() async -> Date {
sleep(5)
return Date()
}
}
결과를 본다면 after async let use time :(Date()) 이부분이 실행되어서 start와 같은 시간이 출력되었다 async-let과 await사이 코드들은 tasksTooLong 함수와 동시에 실행된다
오류 핸들링
오류를 핸들링하기 위해서 throws를 추가하고 enum으로 에러를 만들어주었다
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
42
43
import SwiftUI
enum TaskError: Error {
case tooLong
case tooShort
}
struct ContentView: View {
var body: some View {
Button(action: {
Task{
await doSomething()
}
}){
Text("do something")
}
}
func doSomething() async {
print("Start \(Date())")
do {
try await takesTooLong(delay: 25)
} catch TaskError.tooLong {
print("Too Long")
} catch TaskError.tooShort {
print("Too Short")
} catch {
print("Error: \(error)")
}
print("End \(Date())")
}
func takesTooLong(delay: UInt32 = 1) async throws {
if delay < 5 {
throw TaskError.tooLong
} else if delay > 20 {
throw TaskError.tooShort
}
sleep(delay)
print("Result: \(delay)")
}
}