요즘 뭔가 면접질문에 대해 공부하다보니 자연스럽게 비동기 쪽을 자주 접하게 되는거 같다. 문법 공부를 시작한지 얼마 되지 않아서 @escaping completion을 잘 알진 못해서 지금이라도 알아야 될 것 같아 호다닥 공부해보자.
@escaping completion
클로저와 Swift 5.5부터 도입된 async
와 await
는 모두 비동기 프로그래밍을 처리하는 방법이다.
그러나 이 두 접근 방식은 작동 방식과 사용의 편리성에서 몇 가지 중요한 차이점을 가지고 있다.
@escaping
클로저는 함수가 반환된 후에도 호출될 수 있는 클로저입니다.
비동기 작업을 완료한 후 결과를 전달하는 데 사용된다
만약 내가 매개변수로 받은 completion에 저장된 클로저를 3초 후에 실행시키고 싶음
그래서 다음과 같이 코드를 짜보면
func chunrangsung(completion: () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
completion()
}
}
이렇게 짤 경우
Escaping closure capures non-escaping parameter 'completion'
라는 오류를 뱉을거임 왜 이런에러가 뜨냐?
우리가 일반적으로 위처럼 아무런 키워드 없이 파라미터로 받는 클로저는 모두 non-escaping closure임
이름 그대로 탈출이 불가능한 클로저임 함수의 "흐름"을 탈출하지 않는 클로저란 뜻으로, 한 마디로 함수가 종료되고 나서 클로저가 실행될 수 없다는 말임
따라서non-escaping closure
의 경우
함수 내부에서 직접 실행하기 위해서만 사용한다. 파라미터로 받은 클로저는 변수나 상수에 대입할 수 없고, 중첩 함수 내부에서 클로저를 사용할 경우, 중첩함수를 리턴할 수 없다.
함수의 실행 흐름을 탈출하지 않아, 함수가 종료되기 전에 무조건 실행 되어야 한다고함.
그러면 파라미터로 받은 closure는 비동기로 사용못하냐? 할 때 사용하는 키워드가 바로
@escaping
임.
주로 콜백 패턴에서 사용됩니다. 비동기 작업이 완료된 후에 결과를 클로저를 통해 반환한다.
아래 함수는 @escaping 키워드를 붙혀준 함수임
func chunrangsung(completion: @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
completion()
}
}
이런 식으로 함수의 타입 선언하기 전에
@escaping
이란 키워드를 붙여주면,
이 클로저는 함수의 실행 흐름에 상관 없이 실행되는 클로저다! 라고 알려주는 것임
콜백 중첩(콜백 지옥)으로 인해 코드가 복잡해질 수 있습니다.
에러 처리가 더 어려울 수 있으며, 콜백 내에서 발생하는 강한 참조 순환을 주의해야 한다.
@escaping completion
이 나온지 꽤 된 키워드이다 보니 코드가 난잡해지고, 에러처리가 힘들뿐더러 콜백지옥에 빠질 수도 있다고 한다 ㅎㄷㄷ.. 무서운 친구임 조심해서 잘 써야함
async
와await
는 Swift의 비동기 프로그래밍을 위한 현대적인 접근 방식입니다. 비동기 함수를 동기 함수처럼 쉽게 작성할 수 있게 해줍니다.
흠 쉽게 작성해준다고? 쉬우면 얼마나 쉽다고 비슷하겠지 함 보자고
우선 Async/Await를 사용하지 않은 예시를 한번 보자
func fetchUserData(completion: @escaping (User?, Error?) -> Void) {
// 사용자 데이터 요청
fetchFromAPI("userEndpoint") { userData, error in
guard let userData = userData, error == nil else {
completion(nil, error)
return
}
// 추가적인 설정 데이터 요청
fetchFromAPI("settingsEndpoint") { settingsData, error in
guard let settingsData = settingsData, error == nil else {
completion(nil, error)
return
}
// 최종 사용자 객체 생성
let user = User(data: userData, settings: settingsData)
completion(user, nil)
}
}
}
// 사용 예시
fetchUserData { user, error in
if let user = user {
print("User fetched: \(user)")
} else if let error = error {
print("Error: \(error)")
}
}
흠 이 코드에서는 여러 비동기 작업을 순차적으로 수행해야 하며, 각 단계마다 에러 처리와 결과 처리가 필요하다. 코드가 중첩되어 복잡해지고, 가독성이 떨어지는거 같다.
async
키워드는 비동기 함수를 정의할 때 사용됩니다.await
는 해당 비동기 함수가 결과를 반환할 때까지 기다리도록 합니다.
그럼 Async/Await를 사용한 개선된 예시를 보자
func fetchUserData() async throws -> User {
// 사용자 데이터 요청
let userData = try await fetchFromAPI("userEndpoint")
// 추가적인 설정 데이터 요청
let settingsData = try await fetchFromAPI("settingsEndpoint")
// 최종 사용자 객체 생성
return User(data: userData, settings: settingsData)
}
// 사용 예시
Task {
do {
let user = try await fetchUserData()
print("User fetched: \(user)")
} catch {
print("Error: \(error)")
}
}
중첩된 콜백이 제거되었고 각 비동기 작업을 순차적으로 명확하게 나타낼 수 있었고,
표준적인 try/catch 문을 사용하여 에러를 처리할 수 있어 에러 처리도 간편해진 코드를 볼 수 있다.
async/await를 사용하면 코드가 훨씬 간결해지고, 동기적 코드와 유사한 방식으로 비동기 작업을 처리할 수 있는거 같다.
코드가 훨씬 더 읽기 쉽고 이해하기 쉬워집니다. 에러 처리가 간편해지며, 중첩된 콜백 없이 여러 비동기 작업을 순차적으로 수행할 수 있습니다.
async/await가 비동기 프로그래밍을 얼마나 간소화하고 개선하는지 알 수 있는거 같았다. 특히 여러 비동기 작업이 연속적으로 필요한 경우, async/await의 장점이 더욱 두드러지게 되는거 같았다.
이렇게 오늘은 정리하면서@escaping completion
과 async/await
의 각각 사용법과 예시, 각각의 차이점을 조금 정리해봤는데 확실히 async/await
를 사용한 경우의 코드가 간결해지는것도 볼 수 있었고, 간편해진 모습을 볼 수 있었다.
하지만 아직 현업에서는 레거시 코드들이 많이 있을거고, 레거시 코드엔 분명히 @escaping completion
로 만들어진 함수도 많이 남아있을 것이 분명하다. 그래서 async/await
가 편하다고 해서 이것만 사용하는 것 보다 @escaping completion
를 한번 더 사용해 보는 것도 나쁘지 않겠다는 생각이 든다. 그럼 20000