
(※ hackingWithSwift의 글을 번역한 것으로 아래 출처를 남겨두었습니다. 약간의 오역이 있을 수 있으니 지적해주시면 감사드리겠습니다.)
매우 중요한 변화!!
동기인 것처럼 매우 복잡한 비동기 코드도 실행할 수 있도록 도입된 것으로 C#과Javascript에는 이미 있는 비슷한 기능인데 아주 간단하게 구현된다.
1. async 키워드 사용해서 함수 만듦
2. await 키워드를 사용해 호출
우리는 이미 completion handler를 사용해서 함수가 끝나고 값을 전달해주는 아주 복잡한 syntax를 구현해왔다. 이렇게 말이다..
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
// Complex networking code here; we'll just send back 100,000 random temperatures
DispatchQueue.global().async {
let results = (1...100_000).map { _ in Double.random(in: -10...30) }
completion(results)
}
}
func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
// Sum our array then divide by the array size
DispatchQueue.global().async {
let total = records.reduce(0, +)
let average = total / Double(records.count)
completion(average)
}
}
func upload(result: Double, completion: @escaping (String) -> Void) {
// More complex networking code; we'll just send back "OK"
DispatchQueue.global().async {
completion("OK")
}
}
위 함수들을 조합해서 서버로부터 100,000개의 날씨 기록을 가져와서, 평균 온도를 계산하여, 서버로 평균 온도 값을 전달하고 응답을 받도록 만드면 다음과 같다. call back 지옥
fetchWeatherHistory { records in
calculateAverageTemperature(for: records) { average in
upload(result: average) { response in
print("Server response: \(response)")
}
}
}
함수의 실행을 block하고 값을 즉각 받는 것이 아니라, 준비가 되면 값을 받도록 하는 completion closure를 사용하기 때문에 함수 하나 하나가 실행되기를 기다려야한다는 것이다.
또한, completion handler를 2번 이상 호출하거나 아예 호출하는 것을 잊어버리는 것, @escaping (String) -> Void 구문이 가독성이 떨어진다는 것, 들여쓰기 식으로 pyramid of doom 문제(콜백 문제), 이를 보완하기 위해 Result 라는 반환값 + Error를 리턴할 수 있는 타입을 도입하게 된 것 등이 있다.
이제는 다음과 같이 코드를 엄청나게 깔끔하게 작성할 수 있게 되었다!!!
func fetchWeatherHistory() async -> [Double] {
(1...100_000).map { _ in Double.random(in: -10...30) }
}
func calculateAverageTemperature(for records: [Double]) async -> Double {
let total = records.reduce(0, +)
let average = total / Double(records.count)
return average
}
func upload(result: Double) async -> String {
"OK"
}
즉, completion: @escaping ([Double]) -> Void로 장황하게 썼던 코드를 async -> [Dobule]로 표현하면 되는 것이다.
함수를 호출할 때는 다음과 같다. 들여쓰기로 콜백콜백이 아닌 변수로 정의하여 넘겨주는 방식이다.
func processWeather() async {
let records = await fetchWeatherHistory()
let average = await calculateAverageTemperature(for: records)
let response = await upload(result: average)
print("Server response: \(response)")
}
enum UserError: Error {
case invalidCount, dataTooLong
}
func fetchUsers(count: Int) async throws -> [String] {
if count > 3 {
// Don't attempt to fetch too many users
throw UserError.invalidCount
}
// Complex networking code here; we'll just send back up to `count` users
return Array(["Antoni", "Karamo", "Tan"].prefix(count))
}
func save(users: [String]) async throws -> String {
let savedUsers = users.joined(separator: ",")
if savedUsers.count > 32 {
throw UserError.dataTooLong
} else {
// Actual saving code would go here
return "Saved \(savedUsers)!"
}
}
func updateUsers() async {
do {
let users = try await fetchUsers(count: 3)
let result = try await save(users: users)
print(result)
} catch {
print("Oops!")
}
}
주의할 점은, 비동기 함수가 마법처럼 다른 코드와 동시적으로(concurrently) 맞물려 실행되는 것이 아니다. 오히려 다수의 비동기 함수들을 순차적으로 계속 실행하는 것이다 (이 부분의 번역은 조금 어려운 것 같네요)
이건 언제 유용하는가?? 미리 결과를 한 번에 계산해놓지 않고 시퀀스로서 값을 처리하고자 할 때 유용하다!!
Swift의 Sequence와 일치하지만, 몇 가지 규칙이 있다.
1. AsyncSequence와 AsyncIterator를 준수해야 함
2. next() 메소드는 반드시 async를 붙여야 함
3. 시퀀스가 종료되려면 next()로부터 nil을 반환해야 함 (이건 Sequence와 같음)
예시를 살펴보자
struct DoubleGenerator: AsyncSequence {
typealias Element = Int
struct AsyncIterator: AsyncIteratorProtocol {
var current = 1
mutating func next() async -> Int? {
defer { current &*= 2 }
if current < 0 {
return nil
} else {
return current
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator()
}
}
1. for await 구문을 사용하여 값을 돌려볼 수 있다
func printAllDoubles() async {
for await number in DoubleGenerator() {
print(number)
}
}
2. map(), compactMap(), allSatisfy() 등 다양한 메소드 지원
func containsExactNumber() async {
let doubles = DoubleGenerator()
let match = await doubles.contains(16_777_216)
print(match)
}
포스팅이 길어지는 것 같아 다음 시간에 마저 알아보도록 하자..이번에 Swift 5.5에 변화가 꽤 많은 것 같아 몇 번에 걸쳐 작성해야할 것 같다.
(출처: https://www.hackingwithswift.com/articles/233/whats-new-in-swift-5-5,
https://www.youtube.com/watch?v=6C0SFPEy_0Y)