🔥 Autoclosures
Swift에서 autoclosure
는 함수의 인자로 전달되는 표현식을 자동으로 감싸는 클로저를 말해요. autoclosure
는 인자를 받지 않고, 호출되면 내부에 감싸진 표현식의 값을 반환하지요. 이러한 문법적 편의성 덕분에 명시적인 클로저 대신 일반 표현식을 작성하여 함수의 매개변수 주위의 중괄호를 생략할 수 있답니다.
autoclosure
를 사용하는 함수를 호출하는 것은 일반적이지만, 그런 종류의 함수를 직접 구현하는 것은 흔하지 않아요. 예를 들어, assert(condition:message:file:line:)
함수는 condition
과 message
매개변수에 대해 autoclosure
를 사용하는데요. condition
매개변수는 디버그 빌드에서만 평가되고, message
매개변수는 condition
이 false
인 경우에만 평가된답니다.
autoclosure
를 사용하면 클로저 내부의 코드가 호출될 때까지 실행되지 않기 때문에 평가를 지연시킬 수 있어요. 부작용이 있거나 계산적으로 비용이 많이 드는 코드의 경우 평가 시점을 제어할 수 있어서 유용하지요. 아래 코드는 클로저가 어떻게 평가를 지연시키는지 보여줍니다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // "5" 출력 let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // "5" 출력 print("Now serving \(customerProvider())!") // "Now serving Chris!" 출력 print(customersInLine.count) // "4"
swift
클로저 내부의 코드에 의해 customersInLine
배열의 첫 번째 요소가 제거되더라도, 클로저가 실제로 호출될 때까지 배열 요소는 제거되지 않아요. 만약 클로저가 호출되지 않으면, 클로저 내부의 표현식은 절대 평가되지 않고, 배열 요소도 제거되지 않습니다. customerProvider
의 타입이 String
이 아니라 () -> String
, 즉 매개변수가 없고 문자열을 반환하는 함수라는 점에 주목하세요.
클로저를 함수의 인자로 전달할 때도 동일한 지연 평가 동작을 얻을 수 있어요.
// customersInLine은 ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serve(customer: { customersInLine.remove(at: 0) } ) // "Now serving Alex!" 출력
swift
위 예제의 serve(customer:)
함수는 고객의 이름을 반환하는 명시적 클로저를 인자로 받아요. 아래 버전의 serve(customer:)
는 동일한 작업을 수행하지만, 명시적 클로저 대신 매개변수 타입에 @autoclosure
속성을 표시하여 autoclosure
를 사용합니다. 이제 customerProvider
매개변수의 타입이 @autoclosure
속성으로 표시되었기 때문에, 클로저 대신 String
인자를 사용하는 것처럼 함수를 호출할 수 있어요. 인자는 자동으로 클로저로 변환됩니다.
// customersInLine은 ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // "Now serving Ewa!" 출력
swift
escape가 허용되는 autoclosure
를 원한다면, @autoclosure
와 @escaping
속성을 모두 사용하세요. @escaping
속성은 위에서 Escaping Closures에 설명되어 있답니다.
// customersInLine은 ["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.") // "Collected 2 closures." 출력 for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // "Now serving Barry!" 출력 // "Now serving Daniella!" 출력
swift
위 코드에서 collectCustomerProviders(_:)
함수는 customerProvider
인자로 전달된 클로저를 호출하는 대신 customerProviders
배열에 추가해요. 배열은 함수의 범위 밖에서 선언되므로, 함수가 반환된 후에도 배열의 클로저를 실행할 수 있습니다. 그 결과, customerProvider
인자의 값은 함수의 범위를 벗어날 수 있어야 해요.
이렇게 autoclosure
를 사용하면 코드의 가독성을 높이면서도 성능을 향상시킬 수 있답니다. 평가를 지연시켜 불필요한 연산을 피할 수 있고, 문법적 편의성으로 코드를 간결하게 작성할 수 있지요.
하지만 autoclosure
를 남용하면 코드의 의도가 불명확해질 수 있으니 주의해야 해요. 꼭 필요한 경우에만 사용하고, 함수의 동작을 명확하게 문서화하는 것이 좋답니다.
Swift의 autoclosure
는 강력한 기능이면서도 편리한 문법을 제공하는 훌륭한 예시라고 할 수 있어요. 적절히 사용한다면 코드의 가독성과 성능을 모두 높일 수 있습니다.