본문 바로가기
iOS/Swift

[Swift 5.5] Async / Await

by 헤콩 2021. 12. 20.
반응형

Swift 개발을 하다보면 ClosureCompletion Handlers를 사용하는 asynchronous(비동기) 프로그래밍을 많이 하게 된다. 이처럼 많은 비동기 작업, 오류 처리, 비동기 호출 간의 제어 흐름이 복잡할 때 문제가 된다.

 

많은 비동기 작업에는 수많은 tab이 들어간 deeply-nested closures가 필요하게 되고, 콜백은 오류 처리를 매우 어렵고 장황하게 만든다. 비동기 호출 간의 제어 흐름이 복잡할 때는 어떠한 closurecompletion handler에서 사용될 때, capture에 대해 신중하게 생각해야 하는 상황이 발생할 수도 있다.

 

그래서 나오게 된 것이 asyncawait다.

asyncawait는 Swift 5.5부터 새로운 비동기 프로그래밍 패턴으로 동시성(Concurrency) 개념이 지원되면서 도입된 키워드이다.

 

 

 

Async

async 키워드는 비동기 태스크를 정의한다. → 해당 함수를 비동기 함수로 만들겠다!!!

  • async와 throws를 같이 쓸 수 있다.
    •  
    • func someWork() async throws -> String { ... }
  • 프로토콜에서도 요구 가능하다.
    • protocol SomeDelegate {
          func process() async
      }

 

 

 

Await

await 키워드는 비동기 함수의 실행을 동기화시키는 명령이다. 즉, 현재 스레드에서 해당 비동기 함수의 실행이 끝날 때까지 대기하게 된다.

func doWhatWithSomeWork() async {
    ...(1)...
    try? await someWork()
    ...(2)...
}

await는 블로킹(blocking)을 유발하기 때문에 async로 마크된 비동기 함수 내에서만 쓸 수 있다. 위 코드에서는 (1) 코드가 실행되다가 someWork( )가 실행되면, someWork( )의 작업이 끝날 때까지 (2) 코드는 실행되지 않는다.

 

 

 

병렬 실행

한 번에 여러 작업을 동시에 실행하고자 할 때는 아래와 같은 async-let 문법의 코드를 작성할 수 있다.

async let james = someWork(name: "James")
async let conrad = someWork(name: "Conrad")
async let michael = someWork(name: "Michael")

let results = await [james, conrad, michael]

 

이 외에도 Task Groups 를 활용하는 방법도 있다.

Task Groups은 동적으로 병렬 태스크를 구성해야할 때 유용한 방식인 것 같다. 해당 방식 외에도 부모-자식 태스크를 구성하는 등 다양한 용도로 사용되기도 한다.

await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        taskGroup.async { await downloadPhoto(named: name) }
    }
}

 

 

 

Actors

Swift 5.5부터는 async, await 말고도 actor라 불리는 새로운 타입도 생겨났다. 기존의 클래스와 비슷하게 특수 구조체를 설계할 수 있는 Reference Type이면서 스레드 안정성을 보장하기 위해 동시에 하나의 Task에서만 접근을 보장한다.

 

그렇다고 클래스와 완전히 동일한 것은 아니다.

  • var 로 선언된 가변형 프로퍼티는 외부에서 수정 시에는 외부와 격리되는 규제를 받는다.
  • actor의 프로퍼티와 메서드를 외부에서 참조하려면 반드시 await 로 접근해야 한다.
actor TemperatureLogger {
    let label: String
    var measurement: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement > max {
            max = measurement
        }
    }
}

 

 

아직 개인적으로도 async , await , actor 등에 대해 더 공부해보아야 하고, 실무에서도 아직까지는 많이 사용되는 편은 아닌 것 같다. 하지만 언젠가 코드에 적용하게 된다면 비동기 프로그램이 더 가독성 좋게 개선될 것 같다는 생각이 든다.

 

 

 

Reference

반응형

댓글