안녕하세요 띵지니어 입니다. 😼
escaping 클로저에 대해서 포스팅 해보려고 합니다.
@escaping
개발을 하다 보면 위와 같은 키워드를 볼 수 있습니다.
오늘은 이 키워드에 대해서 알아보려고 합니다.
개념
escaping 클로저의 정의는 다음과 같습니다.
함수에 매개변수로 클로저를 전달할 때, 함수가 return 된 후 호출되는 클로저를 "함수를 탈출(escape)한다"라고 합니다.
클로저를 파라미터로 가지는 함수를 선언할 때, 이 클로저는 탈출을 허락한다는 의미로 파라미터의 타입 앞에 @escaping을 작성할 수 있습니다.
쉽게 설명하면
@escaping 키워드가 클로저앞에 붙으면, 함수 종료 후에도 호출이 가능하구나~
라고 이해하면 됩니다.
여기서 중요한 것은 함수 종료 후에도 호출이 된다는 점입니다!!
조금만 더 알아봅시다
non-escaping vs escaping
예시로 외부에서 클로저 파라미터를 함수 인자로 받고, 해당 클로저를 실행시키는 코드를 먼저 구현해 봅시다.
클로저 타입 앞에 아무것도 없다면 기본적으로 non-escaping 입니다!
import Foundation
// 2. 클로저를 파라미터로 받아서 실행하는 함수
func noneEscapingClosure(completionHandler: () -> Void) {
// 3. 전달받은 클로저를 실행
completionHandler()
}
while true {
let read = readLine()!
if read == "exit" {
break
}
// 1. 함수를 호출하면서 클로저를 전달
noneEscapingClosure {
// 4. 클로저 내부에서 전달받은 입력값을 출력
print("ㅎㅇ \(read)")
}
}
예상한 대로, 클로저 파라미터가 바로 실행이 되는 것을 볼 수 있습니다.
하지만 네트워크 통신 같은 비동기 처리로 인해 3초 정도 지연 된다고 생각해 봅니다.
아래처럼 함수 내부를 수정하고, 다시 컴파일을 돌려보면
func noneEscapingClosure(completionHandler: () -> Void) { // ❗️ Error!
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
completionHandler()
}
}
에러가 뜹니다!
현재 completionHandler는 기본적으로 non-escaping 클로저이기 때문인데요. 즉, 함수 호출이 끝나면 사라지는 클로저로 설계되었습니다.
하지만, 함수 호출이 끝나도 저희는 3초 뒤에 해당 클로저가 실행되게 하고 싶습니다.
따라서 해당 클로저는 함수의 실행 범위를 넘어 escaping 되어야 하므로 이런 상황에서 @escaping 키워드를 사용합니다
func escapingClosure(completionHandler: @escaping () -> Void) { // ✅
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
completionHandler()
}
}
따라서 수정된 함수를 다시 돌려보면, 함수가 끝났는데도 불구하고 아래와 같이 completionHandler가 3초 뒤에 호출되는 것을 알 수 있습니다.
다시 정리해보면
@escaping 키워드를 사용하면, 함수가 종료된 이후에도 클로저(completionHandler)가 비동기적으로 실행되거나 다른 곳에서 호출될 수 있습니다.
활용
이 키워드 그래서.. 어떤 경우에 활용을 하는데요?
네트워크 작업과 같은 비동기 작업에서 많이 사용이 됩니다.
@escaping 키워드가 없을 경우, 클로저는 함수의 생명 주기 내에서만 호출될 수 있습니다.
이 상황에서는 3초를 기다리는 작업이 함수 내부에서 완료될 때까지 메서드가 return 되지 않으므로, 다른 작업을 수행할 수 없게 됩니다.
결과적으로 사용자가 버튼을 눌렀을 때 3초 동안 기다려야 하는 비효율적인 사용자 경험을 유발할 수 있습니다.
하지만 @escaping 키워드를 활용하면, 클로저를 함수의 생명 주기 바깥에서도 호출할 수 있습니다.
이렇게 하면 3초를 기다리는 작업이 비동기적으로 처리되며, 앱은 동시에 다른 작업을 수행할 수 있습니다.
따라서 앱의 반응성이 개선되고, 사용자가 느끼는 버벅거림이나 지연을 줄일 수 있습니다.
아래는 제가 만든 예제 코드와 영상입니다.
import Foundation
enum TestError: Error {
case invalidValue
}
func getNetworkData(number: Int, completion: @escaping (Result<String, TestError>) -> Void) {
let url = URL(string: "http://numbersapi.com/\(number)")!
print("⭐️ getNetworkData 함수 시작")
defer { print("⭐️ getNetworkData 함수 종료") }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
completion(.failure(.invalidValue))
return
}
guard let data = data else {
completion(.failure(.invalidValue))
return
}
if let resultString = String(data: data, encoding: .utf8) {
print("✅ [Success] 데이터 받음 \(resultString)")
completion(.success(resultString))
} else {
completion(.failure(.invalidValue))
}
}.resume()
}
while true {
let number = Int(readLine()!)!
if number == 0 {
break
}
getNetworkData(number: number) { result in
print("🎯 completion 파라미터 실행")
switch result {
case .success(let data):
print("✅ Success: \(data)")
case .failure(let error):
print("❌ Error: \(error)")
}
}
print("➡️ getNetworkData 함수 요청 ... ")
}
실제로 함수 종료를 했음에도, 외부에서 데이터 값을 받는 것을 확인할 수 있습니다.
따라서, 실제로 비동기적으로 실행된 getNetworkData() 의 결과를 확인할 수 있으며, 네트워크 통신이 완료된 후에도 completion 클로저 파라미터를 통해 값을 전달받아 처리할 수 있었습니다.
이는 함수의 실행이 종료된 이후에도 @escaping 키워드를 사용함으로써 클로저를 비동기적으로 안전하게 실행할 수 있게 되었음을 보여줍니다.
마무리
하지만 @escaping 을 사용하면
콜백지옥이라는 단점과 에러핸들링이 어려운 특징이 있습니다.
이를 해결하기 위해 Swift 5.5 부터 Async Await 개념이 도입 되었습니다.
Swift Concurrency에 대한 내용은 추후 포스팅 하겠습니다.
감사합니다.
Swift 6.0
Xcode 16.2
MacOS Sequoia 15.1.1
환경에서 작성 한 글입니다.
참고자료
https://bbiguduk.gitbook.io/swift/language-guide-1/closures#escaping-closures
https://babbab2.tistory.com/164
https://velog.io/@parkgyurim/Swift-escaping-closure
'Swift' 카테고리의 다른 글
백준 14501번 퇴사 Swift (2) | 2024.11.12 |
---|---|
[Swift] Combination(조합) (1) | 2024.06.16 |
[Swift] Optional Unwrapping (4) - 옵셔널 체이닝 (Optional Chaining) (2) | 2024.04.19 |
[Swift] Optional Unwrapping (3) - 닐 코얼레싱 (Nil-Coalescing) (0) | 2024.01.18 |
[Swift] Optional Unwrapping (2) - 옵셔널 바인딩 (Optional Binding) if let , guard let (1) | 2024.01.15 |