저번 글에 이어서 오토클로저에 대해서도 알아보자
오토클로저는 함수의 인자로 전달되는 표현식을 자동으로 래핑하는 클로저다.
어떤 인수도 받지 않으며 호출될 때 그 안에 래핑된 표현식의 값을 반환한다.
이런 문법적 편의성을 통해 명시적인 클로저 대신 일반 표현식을 사용하여 중괄호를 생략할 수 있다.
오토클로저를 사용하면 평가를 지연시킬 수 있다. 클로저를 호출할 때까지 내부 코드는 실행되지 않는다. 이런 작동방식은 부작용을 가진 코드나 계산 비용이 많이 드는 코드를 제어하는데 유용하다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"
위의 코드를 보면 customerProvider에 custersInLine 요소를 제거하고 요소를 반환하는 클로저를 할당 하지만 이 시점에서 해당 코드가 실행되지 않는다, customerProvider을 호출하기 전까지는 클로저가 작동하지 않고 있다.
customerProvider의 타입은 String이 아닌 () -> String이다. 즉 배열의 요소를 제거하고 해당 요소의 반환한 값을 가지는게 아닌 동작을 가지는거라고 이해하면 된다. 이 동작은 매개 변수가 없고 문자열을 반환하는 클로저를 나타낸다.
위에서 설명한 상황처럼 클로저를 함수의 인자로 전달하는 경우에도 클러저는 호출되기 전까지 동작하지 않는 지연 평가 동작을 한다.
// 1.
// 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!"
// 2.
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
1번 코드의 예시의 seve(customer:) 함수의 인자로 클로저를 명시적으로 표현하고 있다. 2번의 경우 1번과 동일한 작업을 수행하지만 @autoclosure 속성을 표시해서 오토클로저를 사용하여 마치 문자열 인자를 사용한 것처럼 호출하고 있다.
@autoclosure 속성으로 표시했기 때문에 인자가 자동으로 클로저로 변환 된것이다. 이렇게 작성하면 코드를 간결하게 작성할 수 있다.
Auto Closure를 너무 과하게 사용하면 코드를 이해하기 어려울 수 있음!
컨텍스트와 함수 이름을 통해 평가가 지연되고 있음을 명확히 표현해야 함!
만약 Auto Closure를 escape 하게 처리하고 싶다면 아래의 코드처럼 '@autoclosure @escaping' 이렇게 같이 작성하면 된다.
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
collectCustomerProviders 함수는 인자로 전달된 customerProvider는 함수 내에서 즉시 실행하지 않고 배열에 저장하여 나중에 실행할 수 있도록 한다.
이 동작을 가능하게 하려면 클로저의 범쉬가 함수 범위를 벗어날 수 있어야 하고 그러기 위해서는 전에 배운 @escaping을 표시하면 된다.
그리고 이번에 배운 @autoClosure도 함께 작성하여 일반 표현식으로 인자에 값을 전달할 수 있다.
역시 클로저를 사용할 때마다 뭔가 제대로 사용하고 있다는 느낌을 들었는데 모르는게 많았다. 특히 @escaping과 @autoClosure는 문서를 보면서 많이 헷갈렸다.
그래도 이제 많이 배웠으니 잘 활용해 보자.