
오늘의 주제는 바로 Swift Concurrency!
전에 비동기 프로그래밍에 대해 다룬 적이 있는데, 사실 Swift에는 Dispatch 프레임워크에 포함된 DispatchQueue를 사용하지 않고도 구조화된 방식으로 비동기 및 병렬 프로그래밍을 작성하기 위한 기능이 담겨있다!
그것이 흔히 Async/Await으로 알려진 Swift Concurrency이다.
대체로 병렬이나 비동기 프로그래밍은 복잡성의 증가를 수반하기 마련인데, Swift Concurrency를 사용하면 컴파일 타임에 체킹할 수 있는 방식으로 의도를 표현할 수 있다. 컴파일 타임에 문제를 파악하는 데 도움이 될 수 있다는 의미이다.
The rest of this chapter uses the term concurrency to refer to this common combination of asynchronous and parallel code.
오늘 참고한 Concurrency 문서에서는 동시성이라는 단어로 비동기 및 병렬의 일반적인 조합을 나타냈다고 하니 이 글에서도 동시성이라고 표현하겠다!
The concurrency model in Swift is built on top of threads, but you don’t interact with them directly. An asynchronous function in Swift can give up the thread that it’s running on, which lets another asynchronous function run on that thread while the first function is blocked. When an asynchronous function resumes, Swift doesn’t make any guarantee about which thread that function will run on.

Swift Concurrency에서는 비동기 작업을 함수로 처리하는데, 이 함수는 어느 스레드에서 실행될지 보장할 수 없다. 그래서 나는 A라는 스레드에서 호출했는데 정작 그 함수는 다른 스레드에서 실행되고 그 사이에 다른 스레드에서 실행한 비동기 함수가 A 스레드에서 실행될 수도 있다.

물론 Swift Concurrency를 사용하지 않고 동시성 코드를 작성하는 것이 가능하지만 해당 코드는 대체로 읽기 더 어렵다고 공식 문서는 말한다. 그러니 한 번 써보자!
비동기 함수나 메서드는 실행 도중에 일시 중지될 수 있는 특별한 종류의 함수(및 메서드)다.
This is in contrast to ordinary, synchronous functions and methods, which either run to completion, throw an error, or never return.
일반적으로 함수는 세 가지 작업(완료(반환), 오류, 반환하지 않음) 중 하나를 수행하는데 그전에는 없었던 일시 중지(suspended)될 수 있는 함수라는 것이다.
그런데 그렇다고 이 비동기 함수가 그 세 작업 중 아무것도 수행하지 않는다는 것은 아니고, 수행하기는 하는데 무언가를 기다리는 동안에 일시 중지가 될 수도 있다는 것이다.

비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일시 중단되는데, 일시 중단 지점을 표시하기 위해 호출 앞에 await 키워드를 쓴다.
오류를 발생시킬 수 있는 함수를 호출할 때
try를 작성하는 것과 같은 맥락이다.
비동기 메서드 내에서 실행 흐름은 다른 비동기 메서드를 호출할 때만 일시 중단된다. 일시 중단될 수 있는 모든 곳에 await 키워드를 작성해야 한다.
자체적으로 오류를 발생시키는 경우
throw를, 다른 오류를 발생시킬 수 있는 함수를 호출할 경우try를 작성하는 것을 하나로 합친 느낌이다.
강제적이긴 하지만 이렇게 모든 일시 중단 지점을 표시하면 동시성 코드를 더 쉽게 읽고 이해할 수 있다.
함수의 비동기 여부는 async 키워드로 함수 뒤에 표기하는데, 만약 반환이 있다면 반환 화살표보다 앞에 적으면 된다.
에러까지 발생할 수 있는 함수라면 throws 키워드 앞에 표기하면 된다.
throws의 짝이try라면async의 짝은await이다.
코드에서 await 키워드로 표시된 일시 중단 지점은 비동기 함수가 반환될 때까지 기다리는 동안 현재 코드 부분이 실행을 일시 중지할 수 있음을 나타낸다.

이를 스레드 양보(yielding the thread)라고도 한다. 왜냐하면 뒤에서 Swift가 현재 스레드에서 코드 실행을 일시 중지하고 해당 스레드에서 다른 코드를 실행하기 때문이다.
throws 함수와 다른 점앞서 몇 가지 공통점을 언급했었는데, 중요한 차이점이 있다.
do-catch 구문이나 Result 타입을 활용하면 throws를 표시하지 않은 함수에서도 안전하게 throws 함수를 호출할 수 있다.

그런데 비동기 함수는 그런 거 없다.
The Swift standard library intentionally omits this unsafe functionality — trying to implement it yourself can lead to problems like subtle races, threading issues, and deadlocks.
의도적으로 그렇게 만들어진 것이다.
비동기 시퀀스(Asynchronous Sequences)를 사용하면 한 요소씩 기다릴 수 있다.

for-await-in 구문을 사용하면 각 반복이 시작될 때 요소가 사용가능할 때까지 실행을 일시 중지한다.
let pasta = await cook(.past)
let steak = await cook(.steak)
let risotto = await cook(.risotto)
let order = [pasta, steak, risotto]
serve(order)
음식을 조리해서 제공하는 상황이라고 생각해보자. 이렇게 병렬적으로 작성할 때, 파스타의 조리가 끝나고 나야 스테이크의 조리가 이루어질 것이다.
cook(_:)이라는 함수는 비동기적으로 음식을 조리하는데, 그 작업을 수행할 동안 다른 일을 할 수 있음에도 마냥 기다린 다음에 다음 음식을 만들어야 한다.
만약 만들어진 파스타를 가지고 무언가 작업을 수행해야 한다면(가령 그 위에 토핑을 올린다거나) 이렇게 병렬적으로 await을 호출하는 것이 맞지만 각자의 결과가 서로에게 영향을 미치지 않는다면, 즉 독립적이라면 동시에 처리하도록 할 수도 있다.
async let pasta = cook(.past)
async let steak = await cook(.steak)
async let risotto = await cook(.risotto)
let order = await [pasta, steak, risotto]
serve(order)
비동기 함수를 호출하고 주변 코드와 병렬로 실행되도록 하려면 상수를 정의할 때 let 앞에 async를 쓴 다음, 상수를 사용할 때마다 await를 쓰면 된다.
두 접근 방식에서 고려할 사항은 다음과 같다.
다음 줄의 코드가 해당 함수의 결과에 따라 달라지는 경우(독립적이지 않은 경우) await를 사용한다. 즉, 순차적으로 진행한다.
당장 반환 값이 필요하지 않은 경우(독립적인 경우) async-let을 사용하여 비동기 함수를 호출한다. 즉, 병렬적으로 진행한다.
동일한 코드에서 두 접근 방식을 혼합할 수도 있다.

붕어빵의 계절이다.
붕어빵이 항상 재고가 있어서 돈내면 바로 뿅하고 나오면 좋겠지만, 운이 나쁘다면 붕어빵이 없어서 기다려야 할 수도 있다.
그러니까 붕어빵을 사는 행동은 비동기 함수이다.
await 붕어빵사기()