본문 바로가기
Swift

[Swift] escaping vs non-escaping 차이점에 대해서

by 마라민초닭발로제 2024. 12. 27.

글은 swift에서 function에서 escaping과 non-escpaing이 무엇이 다른지 기록하기 위해 작성되었습니다.

 

1. Escaping Colsure란?

클로저는 함수의 인자로 전달되었지만 함수가 반환된 이후에 호출될 때, 이를 함수에서 벗어나는(escaping) 클로저라고 합니다. 만약 클로저가 함수의 매개변수 중 하나로 전달될 경우, 해당 클로저가 탈출 가능하다는 것을 나타내기 위해 매개변수의 타입 앞에 @escaping 키워드를 작성할 수 있습니다.

 

클로저가 escape할 수 있는 한 가지 방법은, 클로저가 함수 외부에 정의된 변수에 저장되는 경우입니다. 예를 들어, 비동기 작업을 시작하는 많은 함수들은 완료 핸들러(completion handler)로 클로저를 인자로 받습니다. 이러한 함수는 작업을 시작한 후 즉시 반환하지만, 작업이 완료되기 전까지 클로저는 호출되지 않습니다. 따라서 클로저는 나중에 호출될 수 있도록 탈출(escape)해야 합니다.

 

 

2. CompletionHandler를 받을때 왜 @escaping 키워드를 안쓰면 어떻게 될까?

func test1(completion: () -> Void) -> Int{
    let myCompletion: () -> Void = {
        print("complete")
    }
    let request = URLRequest(url: .init(string: "https://example.com")!)
    URLSession.shared.dataTask(with: request) { data, response, error in
        completion() // ❌ 컴파일 에러 발생
    }
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        completion() // ❌ 컴파일 에러 발생
    }
    return 1
}

func test2(completion: @escaping () -> Void) -> Int{
    let myCompletion: () -> Void = {
        print("complete")
    }
    let request = URLRequest(url: .init(string: "https://example.com")!)
    URLSession.shared.dataTask(with: request) { data, response, error in
        completion() // ✅
    }
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        completion() // ✅
    }
    return 1
}

 

 

+ Autoclosure란?

autoclosure는 함수의 인자로 전달되는 표현식을 자동으로 감싸는 클로저입니다. 인자를 받지 않으며, 호출될 때 내부에 감싸진 표현식의 값을 반환합니다. 이러한 문법적 편의성 덕분에 함수의 매개변수 주위에 중괄호를 생략하고 명시적인 클로저 대신 일반 표현식을 작성할 수 있습니다.

 

아래와 같이 오토 클로저를 생성하고 Delay후에 autoclosure를 실행할 수 있습니다. 

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"


let customerProvider = { customersInLine.remove(at: 0) } // ✅ Use autoclosure
print(customersInLine.count)
// Prints "5"


print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

 

위의 autoclosure와 마찬가지로 함수로 다음과 같이 구현할 수 있습니다.

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

 

위 내용에서 나온 serve(customer:) 함수는 고객의 이름을 반환하는 명시적인 클로저를 매개변수로 받습니다. 아래 버전의 serve(customer:) 함수는 동일한 동작을 수행하지만, 명시적인 클로저 대신 매개변수 타입에 @autoclosure 속성을 표시하여 자동 클로저를 받습니다. 이제 이 함수를 클로저 대신 String 인자를 받는 것처럼 호출할 수 있습니다. customerProvider 매개변수의 타입이 @autoclosure 속성으로 표시되어 있기 때문에 인자가 자동으로 클로저로 변환됩니다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))

 

 

실제 오토클로저를 활용한 테스트들 입니다. 

func test3(completion: () -> String) -> String {
    return completion()
}

func test4(completion: @autoclosure () -> String) -> String {
    return completion()
}

func test5() {
    var myArray = ["Minsu", "Susan", "Chan", "Heize"]
    print(test3{ myArray.removeFirst() })
    print(test3(completion: myArray.removeFirst() )) // ❌ Compile Error

    let myClosure: () -> String = {
        myArray.removeFirst()
    }
    print(test4(completion: myClosure()))
    print(test4{ myArray.removeFirst() }) // ❌ Compile Error
    print(test4(completion: myArray.removeFirst()))
}