Swift - Closures 6

 

Swift의 Closures 정리 6 - Autoclosures

Closures #6


Autoclosures

  • autoclosure는 자동으로 표현식을 래핑하여 생성된 후 함수의 인자로 전달되는 closure다.
  • autoclosure는 아무런 인자를 취하지 않으며, 호출될 때 내부에서 래핑된 표현식이 결과 값을 반환한다.
  • autoclosure는 명시적인 closure에 비해 함수의 파라미터를 둘러싼 괄호를 생략할 수 있는 것 처럼 문법적인 편의를 제공한다.
  • autoclosure를 파라미터로 취하는 함수를 호출하는 것은 일반적이나 그런 함수를 구현하는 것은 일반적이지 않다.
  • 예를들어 assert(condition:message:file:line:) 함수는 condition과 message 파라미터로 autoclosure를 취한다. 이 중 condition 파라미터는 디버깅 시에만 평가되고 message 파라미터는 condition이 false인 경우에만 평가된다.
  • autoclosure는 closure를 호출하기 전까지는 내부 코드가 실행되지 않기 때문에 평가를 지연시킨다.
  • 평가 지연은 코드가 평가되기 전에 제어가 가능하기 때문에 버그 발생 가능성이 있는 코드나 계신 비용이 비싼 코드에 유용하다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 5 출력

// closure가 선언되는 시점에는 closuer 내부의 코드가 실행되지 않는다.
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 5 출력

// closure가 호출되는 시점에 closuer 내부의 코드가 실행된다.
print("Now serving \(customerProvider())!")
// "Now serving Chris" 출력
print(customersInLine.count)
// 4 출력
  • customerProvider의 타입이 String이 아니라 () -> String 타입의 함수인 것에 주의하라.
  • 아래 예제는 closuer가 함수의 인자로 전달 되는 경우의 지연 평가에 대한 코드다.
// customerInLine은 ["Alex", "Ewa", "Barry", "Daniella"]인 상태
func serve(customer customerProvider: () -> String) {
	print("Now serving \(customerProvider())!")
}

serve(customer: { customersInLine.remove(at: 0) })
// "Now serving Alex" 출력
  • 위 예제에서 serve(customer:) 함수는 명시적으로 closure를 파라미터로 받고 String(고객 이름)을 리턴한다. 아래 예제는 동일한 기능을 하지만 파라미터 타입에 @autoclosure를 적어 autoclosure를 취한다.
  • 이 상태에서는 String 파라미터를 사용하는 것 처럼serve(customer:) 함수를 호출할 수 있다.
// customerInLine은 ["Ewa", "Barry", "Daniella"]인 상태
func serve(customer customerProvider: @autoclosure () -> String) {
	print("Now serving \(customerProvider())!")
}

serve(customer: customersInLine.remove(at: 0))
// "Now serving Alex" 출력

autoclosure를 지나치게 자주 사용하게 되면 코드의 가독성을 떨어뜨린다. 따라서 autoclosure를 사용할 경우에는 context나 함수 이름에서 지연 평가가 일어나고 있음을 분명히 표현해야 한다.

  • autoclosure에 탈출을 허용하고자 할 경우에는 @autoclosure와 @escaping을 같이 사용하면 된다.
// customerInLine은 ["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 \(customersInLine.count) closures.")
// "Collected 2 closures" 출력
for customerProvider in customerProviders {
	print("Now serving \(customerProvider())!")
}
// "Now serving Barry" 출력
// "Now serving Daniella" 출력