Chapter 2: Getting Started
Pre-async/await asynchrony
- async/await 이전에는 어떻게 쓰고 있었나 돌아보기
- completion closure 열심히 사용함
func viewDidAppear() {
api.fetchServerStatus { [weak viewModel] status in
guard let viewModel = viewModel else { return }
viewModel.serverStatus = status
}
}
언뜻 보기엔 간단함
위험성이 있음: completion 호출을 빼먹을 수 있고, 에러 처리를 안하고 갈 수도 있음
기존에는 GCD 기반이라서, swift에 긴밀하게 통합하진 못했음
컴파일러 입장에서 completion은 언제, 몇번이나 호출될지 알 수 없어서, 생명주기나 메모리 최적화 어려움
completion 안에서 weak으로 캡쳐해서 메모리 관리 해줘야 함
컴파일러가 에러 처리를 보장해주기 어려움
modern concurrency model에서는 개선됨
async
: asynchronous 인지 키워드로 알려줌await
: 코드가 어느지점에서 대기할지 알려줌Task
: unit of asynchronous work. 여기서 비동기 동작 수행하면 됨
func viewDidAppear() {
Task {
viewModel.serverStatus = try await api.fetchServerStatus()
}
}
Separating code into partial tasks
“the code might suspend at each await”: 각 await 마다 suspend 될 수도 있음. 이게 무슨 뜻인지 알아봅시다!
- 공유 자원(CPU, memory) 최적화를 위해서
partial tasks
orpartials
라는 논리단위로 나눔
- 각
partials
가 어느 스레드에서 작동할지 모름 - 각 partial이 끝날 때, 코드를 계속 진행할지 아니면 다른 task 실행할지 시스템이 정함
- 정리: 최적화에 의해서 동시에 다른 작업도 실행될 수 있음을 기억해야 한다는 뜻
Executing partial tasks
- 비동기 코드를 나눠서
Executor
위에서 실행시킴 Executor
는 GCD 닮음. 근데 더 강력함.- 신속한 실행. 실행 순서, 스레드 관리 같은 복잡성이 숨겨짐
이거 보면서 프레임워크가 일을 잘해주면 클라이언트가 비즈니스의 본질에 더 집중 할 수 있다는 것을 느낌
Controlling a task’s lifetime
- 비동기 코드 수명 관리
- 기존 단점: 코드 실행 후 CPU 코어를 회수하기 어려움
- 작업이 없을 때도 리소스를 차지할 떄가 있다는 뜻
- ex) 서버 API 2번 호출하면, 불필요한 호출은 리소스 낭비
- async/awiat 에서는 child Task 까지 한번에 취소 가능
- 현재 작업이 취소되었는지 확인 후, 수동으로도 취소 가능
- 상위 Task로 에러 전파도 가능
문법
func funcName() async throws -> String {
...
}
let myVar = try await funcName()
병렬적으로 호출해도 되는 async 함수
async let
으로 정의하면 됨
do {
async let files = try model.availableFiles()
async let status = try model.status()
...
let (filesResult, statusResult) = try await (files, status)
} catch {
lastErrorMessage = error.localizedDescription
}