서론
async / await에 대한 Swift 가이드를 읽으면서 한숨이 푹 나왔습니다.
이게 무슨 말인지 모르겠고... 단어도 어렵고 ㅎㅎ;;
async / await에 대해 따로 정리해야할 필요성을 느껴 포스팅을 하게 되었습니다.
아마 async / await와 task, actor에 대한 것은 꾸준히 공부하면서 글을 작성할 듯 합니다.
아무튼 이번 포스팅은
깊은 개념 정리가 아니라 간단 정리로 생각해주시면 감사하겠습니다.
동시에 간단 실습도 함께 진행합니다!
async / await에 대한 Swift 가이드는 여기에서 확인할 수 있습니다.
async / await 탄생 배경
async / await는 비동기를 처리하는 기능입니다.
기존에는 completion handler는
에러일 때 completion handler 호출을 잊어버리는 문제,
동기적으로 수행해야할 비동기 함수 호출이 많으면 코드 depth가 깊어지는 문제,
실패, 성공에 따른 코드 분기가 복잡하다는 문제가 있습니다.
저도 여기 에서 말했던 것처럼 카멜레온 개발을 하면서 completion handler의 단점을 몸소 체험했었죠.
Swift 5.5에서 async / await가 탄생했습니다.
바로 completion handler의 단점을 해결하기 위해서요!
async / await는 비동기 코드를 마치 동기 코드인 것처럼 작성할 수 있기 때문에
동일한 언어 구조를 최대한 활용할 수 있습니다.
completion handler를 이용해 이렇게 복잡했던 코드가
모든 클로저와 depth가 사라진 깔끔한 코드가 되었습니다.
이렇게 async/await는 기존 completion handler보다 가독성이 월등히 좋고 간편합니다.
이제 그럼 async/await를 어떻게 쓰는지 알아봅시다.
async
async는 함수가 비동기로 처리된다는 것을 의미합니다.
async 키워드는 함수 명 오른쪽에 표시해야 합니다.
throws를 같이 붙여야 할 때는 async throws로 쓰면 돼요.
func asyncTest() async throws -> String {
...
}
프로토콜에서도 요구 가능합니다.
protocol SomeProtocol {
func test() async
}
이렇게 하면 SomeProtocol을 채택해서 test()를 정의할 때 async 함수로 정의해야 합니다.
await
async 함수를 호출하기 위해서는 await 키워드가 필요합니다.
throws와 try가 한 쌍인 것처럼 async와 await가 한 쌍인거에요.
await로 마킹된 곳은 potential suspension point(잠재적 일시 중단 지점)로 지정됩니다.
async로 선언한 함수가 완료될 때까지 일시 중지 되는 지점입니다.
예를 들어, 네트워크로 요청한 데이터를 다 가져올 때까지 작업을 일시 중단 해야겠죠?
async로 데이터 요청을 하고 await 지점에서 대기하는 것입니다.
let string: String = try await asyncTest()
이런식으로 쓰면 돼요.
asyncTest()가 완료될 때까지 해당 Task의 작업은 일시 정지 됩니다.
사실 potential suspedsion points는 이렇게 간단한 개념이 아닙니다.
여기에서 자세한 내용을 볼 수 있는데요. 이 내용은 추후 포스팅을 따로 하도록 하겠습니다.
간단 실습
Swift에서 간단한 실습을 해보겠습니다.
async 함수 정의하기
async 함수를 정의해 볼게요. (사실 위에서 본 함수임 ㅎ;)
func asyncTest() async throws -> String {
try await Task.sleep(nanoseconds: 3_000_000_000) //3초
return "sleep 끝"
}
3초동안 대기하다 "sleep 끝" 문자열을 반환하는 함수입니다.
위 함수를 동기적으로 처리하면 3초동안 프로그램이 멈추는 아주 끔찍한 사태가 발생하기 때문에
반드시 비동기적으로 처리해야 합니다.
함수명인 asyncTest 옆에 async를 붙여주고
Task.sleep에서 에러를 낼 가능성이 있으므로 throws도 붙여줍니다.
좀 어이 없던게 Task.sleep는 Swift 가이드에서도 나와 있는데요.
비동기 간단 테스트 코드로 Task.sleep(until:clock:) 메서드가 소개되어 있는데
링크에 들어가보니
사라졌더라고요 ㅎ..
최신 정보로 업데이트 좀 후딱후딱 해줬으면 좋겠습니다 ㅎㅎ;;
await 코드 작성하기
async와 한 쌍인 await를 작성해야 합니다.
let string: String = try await asyncTest()
try는 Task.sleep()을 위한 throws 때문에 붙인 키워드이고,
async와 짝꿍인 await만 함수명 앞에 작성합니다.
(좀 특이한건 async throws인데 얘는 try await인거네요. 영어에서는 저게 더 문맥상 어울리나봐요.)
근데 그냥 작성하면 컴파일 에러가 발생합니다.
async 메서드를 호출하려면 async 메서드 내에서 호출되거나
Task로 묶어서 호출해야 합니다.
Task {
let string: String = try await asyncTest()
print(string)
}
그래서 이렇게 Task { } 로 async/await 쌍을 묶으면 정상적으로 실행이 됩니다.
위 코드를 실행시키면 3초 후 sleep 끝 메시지가 출력될 것입니다.
Task 주의점
여기서 한 가지 주의할 점은!
Task들은 각자 병렬적으로 수행된다는 건데요.
아래와 같은 코드가 있을 때
print("Before Task")
Task {
let string: String = try await asyncTest()
print(string)
}
print("After Task")
출력은 Before Task -> After Task -> 3초 대기.... -> sleep 끝 이 됩니다.
착각하시면 안 되는 중요한 부분이기 때문에 굳이 한 번 언급해보았습니다.
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.