Swift

[Swift] @escaping 클로저 (escaping closure)

띵지니어 2024. 12. 18. 23:22
반응형

안녕하세요 띵지니어 입니다. 😼
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 개념이 도입 되었습니다.

 

Documentation

 

docs.swift.org

Swift Concurrency에 대한 내용은 추후 포스팅 하겠습니다.

감사합니다.

Swift 6.0
Xcode 16.2
MacOS Sequoia 15.1.1
환경에서 작성 한 글입니다.

 

참고자료

https://bbiguduk.gitbook.io/swift/language-guide-1/closures#escaping-closures

 

클로저 (Closures) | Swift

명명된 함수 생성없이 실행되는 코드 그룹입니다. 클로저 (Closures) 는 코드에서 주변에 전달과 사용할 수 있는 자체 포함된 기능 블럭입니다. Swift의 클로저는 다른 프로그래밍 언어에서 클로저,

bbiguduk.gitbook.io

https://babbab2.tistory.com/164

 

Swift) closure와 @escaping 이해하기

안녕하세요? :> 오랜만입니다 오랜만인 이유는 5, 6월 현업이 매우매우x100 바쁘기도 했고, 그에 따른 번아웃이 온 건지,, 한 달정도 공부에 대한 반항을 좀 해서,, ㅎㅎ;; 쨌든 다시 정신 차리고 돌

babbab2.tistory.com

https://velog.io/@parkgyurim/Swift-escaping-closure

 

[Swift] @escaping 클로저

📚 Swift @escaping 클로저에 대해 알아보자

velog.io

 

반응형
목차(index)