Post

Swift의 동시성

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)")
    }
}
This post is licensed under CC BY 4.0 by the author.