클로저 (Closures) - 자동 클로저 (Autoclosures)

00yhsp·2024년 4월 8일

자동 클로저 (autoclosure) 는 함수에 인수로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저이다.
인수를 가지지 않으며 호출될 때 내부에 래핑된 표현식의 값을 반환한다.
이러한 구문상의 편의를 통해 명시적 클로저 대신에 일반 표현식을 작성하여 함수의 파라미터 주위의 중괄호를 생략할 수 있다.

자동 클로저를 가지는 함수를 호출 하는 것은 일반적이지만 이러한 함수를 구현 하는 것은 일반적이지 않다. 예를 들어 assert(condition:message:file:line:) 함수는 condition 과 message 파라미터에 대한 자동 클로저를 가진다. condition 파라미터는 오직 디버그 빌드인지 판단하고 message 파라미터는 condition 이 false 인지만 판단한다.

클로저가 호출될 때까지 코드 내부 실행이 되지 않기 때문에 자동 클로저는 판단을 지연시킬 수 있다.
판단 지연은 코드 판단 시기를 제어할 수 있기 때문에 사이드 이펙트가 있거나 계산이 오래 걸리는 코드에 유용하다.
아래 코드는 클로저가 어떻게 판단을 지연하는지 보여 준다.

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"

클로저 내부의 코드에 의해 customersInLine 배열의 첫번째 요소는 삭제되지만 클로저가 실제로 호출되기 전까지 삭제되지 않는다. 클로저가 호출되지 않으면 클로저 내부의 표현식은 판단되지 않는다. 이것은 배열의 요소가 삭제되지 않는다는 의미이다. customerProvider 타입은 String 이 아니고 파라미터가 없고 문자열을 반환하는 () -> String 이다.

함수의 인수로 클로저를 전달하면 위와 같은 지연 판단과 동일한 동작을 가질 수 있다.

// 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 속성으로 표시되므로 인수는 자동으로 클로저로 변환된다.

Note:
자동 클로저 남용은 코드 이해를 어렵게 만들 수 있다.
컨텍스트와 함수 이름은 판단이 연기되고 있음을 분명히 해야 한다.

자동 클로저가 이스케이프를 허용하길 원한다면 @autoclosure 와 @escaping 속성을 둘다 사용하면 된다. @escaping 속성은 위의 탈출 클로저 (Escaping Closures) 에서 설명되어 있다.

// 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!"

위의 코드에서 customerProvider 인수로 전달된 클로저를 호출하는 대신에 collectCustomerProviders(_:) 함수는 클로저를 customerProviders 배열에 추가한다. 이 배열은 함수의 범위 밖에 선언된다. 이것은 배열에 클로저는 함수가 반환된 후에 실행될 수 있다는 의미이다. 그 결과 customerProvider 인수의 값은 함수의 범위를 벗어날 수 있어야 한다.

profile
iOS Dev

0개의 댓글