클로저는 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것을 말한다. 함수는 클로저의 한 형태이다.
클로저는 변수나 상수가 선언된 위치에서 참조(Reference)를 획득(Capture)하고 저장할 수 있다. 이를 변수나 상수의 클로징(잠금)이라고 하며 클로저는 여기서 착안된 이름이다.
클로저에는 세 가지 형태가 있다.
- 이름이 있으면서 어떤 값도 획득하지 않는 전역함수의 형태
- 이름이 있으면서 다른 함수 내부의 값을 획득할 수 있는 중첩된 함수의 형태
- 이름이 없고 주변 문맥에 따라 값을 획득할 수 있는 축약 문법으로 작성한 형태
먼저 클로저를 얼마나 다양하게 표현할 수 있는지 살펴보자.
- 클로저는 매개변수와 반환 값의 타입을 문맥을 통해 유추할 수 있기 때문에 매개변수와 반환 값의 타입을 생략할 수 있다.
- 클로저에 단 한줄의 표현만 들어있다면 암시적으로 이를 반환 값으로 취급한다.
- 축약된 전달인자 이름을 사용할 수 있다.
- 후행 클로저 문법을 사용할 수 있다.
클로저 표현 방법은 클로저가 함수의 모습이 아닌 하나의 블록의 모습으로 표현될 수 있는 방법을 의미한다. 클로저의 위치를 기준으로 크게 기본 클로저 표현과 후행 클로저 표현이 있다.
13.1 기본 클로저
기본 클로저 내용을 포함하여 앞으로 sorted(by:) 메서드를 이용해 동일한 기능을 하는 코드를 어떻게 간결하게 표현하는지 알아보자.
sorted(by:) 메서드는 클로저를 통해 어떻게 정렬할 것인가에 대한 정보를 받아 처리하고 결괏값을 배열로 돌려준다. 단순히 정렬만 하기 때문에 입력받은 배열의 타입과 크기가 동일하다. 기존의 배열은 변경하지 않고 정렬된 배열을 새로 생성하여 반환해준다.
[코드 13-1]은 sorted(by:) 메서드의 정의이다.
/*
코드 13-1. 스위프트 라이브러리의 sorted(by:) 메서드 정의
*/
public func sorted(by areInIncreasingOrder:
(Element, Element) -> Bool) -> [Element]
먼저 [코드 13-2]와 같이 이름 배열을 하나 생성한다.
/*
코드 13-2. 정렬에 사용될 이름 배열
*/
let names: [String] = ["wizplan", "eric", "yagom", "jenny"]
sorted(by:) 메서드는 (배열의 타입과 같은 두 개의 매개변수를 가지며 Bool 타입을 반환하는) 클로저를 전달인자로 받을 수 있다. 반환하는 Bool 값은 첫 번째 전달인자 값이 새로 생성되는 배열에서 두 번째 전달인자 값보다 먼저 배치되어야 하는지에 대한 결괏값이다. true를 반환하면 첫 번째 전달인자가 두 번째 전달인자보다 앞에 온다.
먼저 익숙한 방법대로 매개변수로 String 타입 두 개를 가지며, Bool 타입을 반환하는 함수를 구현해보자. 구현된 함수를 sorted(by:) 메서드의 전달인자로 전달하여 reversed라는 이름의 배열로 반환받는다.
/*
코드 13-3. 정렬을 위한 함수 전달
*/
func backwards(first: String, second: String) -> Bool {
print("\(first) \(second) 비교중")
return first > second
}
let reversed: [String] = names.sorted(by: backwards)
print(reversed) // ["yagom", "wizplan", "jenny", "eric"]
backwards 함수를 클로저 표현을 이용해서 조금 더 간결하게 표현해보자.
클로저 표현은 통상 아래 형식을 따른다.
{ (매개변수들) -> 반환 타입 in
실행코드
}
클로저도 함수와 마찬가지로 입출력 매개변수를 사용할 수 있다. 매개변수 이름을 지정한다면 가변 매개변수 또한 사용 가능하다. 다만 클로저는 매개변수 기본값을 사용할 수 없다.
이제 backwards(first:second:) 함수를 클로저 표현으로 대체해보자.
/*
코드 13-4. sorted(by:) 메서드에 클로저 전달
*/
// backwards(first:second:) 함수 대신에
// sorted(by:) 메서드의 전달인자로 클로저를 직접 전달
let reversed: [String] =
names.sorted(by: { (first: String, second: String) -> Bool in
return first > second
})
print(reversed) // ["yagom", "wizplan", "jenny", "eric"]
13.2 후행 클로저(Trailing Closure)
[코드 13-4]보다 조금 더 클로저를 읽기 쉽게 바꿔볼 수 있다. 함수나 메서드의 마지막 전달인자로 위치하는 클로저는 함수나 메서드의 소괄호를 닫은 후 작성해도 된다. 클로저가 조금 길어지거나 가독성이 조금 떨어진다 싶으면 후행 클로저 기능을 사용하면 좋다.
단, 후행 클로저는 맨 마지막 전달인자로 전달되는 클로저에만 해당되므로 전달인자로 클로저 여러 개를 전달할 때는 맨 마지막 클로저만 후행 클로저로 사용할 수 있다. 또한 sorted(by:) 메서드처럼 단 하나의 클로저만 전달인자로 전달하는 경우에는 소괄호를 생략해줄 수도 있다.
또, 매개변수에 클로저가 여러 개 있는 경우, 다중 후행 클로저 문법을 사용할 수 있다. 다중 후행 클로저를 사용하는 경우, 중괄호를 열고 닫음으로써 클로저를 표현하며, 첫 번째 클로저의 전달인자 레이블은 생략한다.
[코드 13-5]는 세 가지 경우를 모두 사용해본 코드이다.
/*
코드 13-5. 후행 클로저 표현
*/
// 후행 클로저의 사용
var reversed: [String] =
names.sorted() { (first: String, second: String) -> Bool in
return first > second
}
// sorted(by:) 메서드의 소괄호까지 생략 가능
reversed = names.sorted { (first: String, second: String) -> Bool in
return first > second
}
func doSomething(do: (String) -> Void,
onSuccess: (Any) -> Void,
onFailure: (Error) -> Void) {
// do something..
}
// 다중 후행 클로저의 사용
doSomething{ (someString: String) in
// do closure
} onSuccess: {(result: Any) in
// success closure
} onFailure: {(error: Error) in
// failure closure
}
13.3 클로저 표현 간소화
13.3.1 문맥을 이용한 타입 유추
메서드의 전달인자로 전달하는 클로저는 메서드에서 요구하는 형태로 전달해야 한다. 즉, 전달인자로 전달할 클로저는 이미 적합한 타입을 준수하고 있다고 유추할 수 있다. 그래서 전달인자로 전달하는 클로저를 구현할 때는 매개변수의 타입이나 반환 값의 타입을 굳이 표현해주지 않고 생략하더라도 문제가 없다.
/*
코드 13-6. 클로저의 타입 유추
*/
let reversed: [String] = names.sorted { (first, second) in
return first > second
}
13.3.2 단축 인자 이름
위의 클로저에서 두 매개변수 이름인 first, second를 더 간결하게 표현할 수 있도록 스위프트는 단축 인자 이름을 제공한다.
단축 인자 이름은 첫 번째 전달인자부터 $0, $1, $2, .... 순서로 $와 숫자의 조합으로 표현한다. 단축 인자 표현을 사용하게 되면 매개변수 및 반환 타입과 실행 코드를 구분하기 위해 사용했던 키워드 in을 사용할 필요도 없어진다.
/*
코드 13-7. 단축 인자 이름 사용
*/
// 단축 인자 이름을 사용한 표현
let reversed: [String] = names.sorted {
return $0 > $1
}
13.3.3 암시적 반환 표현
클로저에서는 return 키워드 역시 생략할 수 있다. 만약 클로저가 반환 값을 갖는 클로저이고 클로저 내부의 실행문이 단 한줄이라면, 암시적으로 그 실행문을 반환 값으로 사용할 수 있다.
/*
코드 13-8. 암시적 반환 표현의 사용
*/
// 암시적 반환 표현의 사용
let reversed: [String] = names.sorted {
$0 > $1
}
13.3.4 연산자 함수
클로저는 매개변수의 타입과 반환 타입이 연산자를 구현한 함수의 모양과 동일하다면 연산자만 표기하더라도 알아서 연산하고 반환한다. 이유는 연산자가 일종의 함수이기 때문이다.
/*
코드 13-10. 클로저로서의 연산자 함수 사용
*/
// 연산자 함수를 클로저의 역할로 사용
let reversed: [String] = names.sorted(by: >)
13.4 값 획득
클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 획득(Capture)할 수 있다. 값 획득을 통해 클로저는 주변에 정의한 상수나 변수가 더 이상 존재하지 않더라도 해당 상수나 변수의 값을 자신 내부에서 참조하거나 수정할 수 있다. 클로저는 비동기 작업에 많이 사용된다. 클로저를 통해 비동기 콜백을 작성하는 경우, 현재 상태를 미리 획득해두지 않으면, 실제로 클로저의 기능을 실행하는 순간에는 주변의 상수나 변수가 이미 메모리에 존재하지 않는 경우가 발생한다.
중첩 함수도 하나의 클로저 형태인데, 이 중첩 함수 주변의 변수나 상수를 획득해 놓을 수도 있다. 즉, 자신을 포함하는 함수의 지역변수나 지역상수를 획득할 수 있다.
지금부터 incrementer라는 함수를 중첩 함수로 포함하는 makeIncrementer 함수를 살펴보자. 중첩 함수인 incrementer() 함수는 자신 주변에 있는 runningTotal과 amount라는 두 값을 획득한다. 두 값을 획득한 후에 incrementer는 클로저로서 makeIncrementer 함수에 의해 반환된다.
/*
코드 13-11. makeIncrementer(forIncrement:) 함수
*/
func makeIncrementer(forIncrement amount: Int) -> (() -> Int) {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer 함수의 반환 타입은 () → Int 이다. 이는 함수객체를 반환한다는 의미이다. incrementer가 반환하게 될 값을 저장하는 용도로 runningTotal을 정의했고, 0으로 초기화해두었다.
만약 incrementer() 함수를 makeIncrementer(forIncrement:) 함수 외부에 독립적으로 떨어뜨려 놓으면 동작할 수 없는 이상한 형태가 된다. 그러나 [코드 13-11]처럼 incrementer() 함수 주변에 runningTotal과 amount 변수가 있다면 incrementer() 함수는 두 변수의 참조를 획득할 수 있다. 참조를 획득하면 runningTotal과 amount는 makeIncrementer 함수의 실행이 끝나도 사라지지 않는다. 게다가 incrementer가 호출될 때마다 계속해서 사용할 수 있다.
/*
코드 13-13. incrementByTwo 상수에 함수 할당
*/
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let first: Int = incrementByTwo() // 2
let second: Int = incrementByTwo() // 4
let third: Int = incrementByTwo() // 6
/*
코드 13-14. 각각의 incrementer의 동작
*/
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let incrementByTwo2: (() -> Int) = makeIncrementer(forIncrement: 2)
let incrementByTen: (() -> Int) = makeIncrementer(forIncrement: 10)
let first: Int = incrementByTwo() // 2
let second: Int = incrementByTwo() // 4
let third: Int = incrementByTwo() // 6
let first2: Int = incrementByTwo2() // 2
let second2: Int = incrementByTwo2() // 4
let third2: Int = incrementByTwo2() // 6
let ten: Int = incrementByTen() // 10
let twenty: Int = incrementByTen() // 20
let thirty: Int = incrementByTen() // 30
[코드 13-14]처럼 각각의 incrementer 함수는 언제 호출이 되더라도 자신만의 runningTotal 변수를 갖고 카운트하게 된다. 다른 함수의 영향도 전혀 받지 않는다. 각각 자신만의 runningTotal의 참조를 미리 획득했기 때문이다.
NOTE_클래스 인스턴스 프로퍼티로서의 클로저 클래스 인스턴스의 프로퍼티로 클로저를 할당한다면 클로저는 해당 인스턴스 또는 인스턴스의 멤버의 참조를 획득할 수 있으나, 클로저와 인스턴스 사이에 강한참조 순환 문제가 발생할 수 있다. 강한참조 순환 문제는 획득목록을 통해 없앨 수 있다. 더 자세한 사항은 ARC의 강한참조 순환 문제(27.2절)에서 다룬다.
13.5 클로저는 참조 타입
[코드 13-14]에서 incrementByTwo와 incrementByTen은 모두 상수이다. 이 두 상수 클로저는 값 획득을 통해 runningTotal 변수를 계속해서 증가시킬 수 있다. 왜냐하면 함수와 클로저는 참조 타입이기 때문이다.
함수나 클로저를 상수나 변수에 할당할 때마다 사실은 상수나 변수에 함수나 클로저의 참조를 설정하는 것이다. 즉, incrementByTwo라는 상수에 클로저를 할당한다는 것은 클로저의 내용물, 즉 값을 할당하는 것이 아니라 해당 클로저의 참조를 할당하는 것이다. 결국 클로저의 참조를 다른 상수에 할당해준다면 이는 두 상수가 모두 같은 클로저를 가리킨다는 뜻이다.
/*
코드 13-15. 참조 타입인 클로저
*/
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let sameWithIncrementByTwo: (() -> Int) = incrementByTwo
let first: Int = incrementByTwo() // 2
let second: Int = sameWithIncrementByTwo() // 4
[코드 13-15]를 통해 두 상수는 같은 클로저를 참조하기 때문에 동일한 클로저가 동작하는 것을 확인할 수 있다.
13.6 탈출 클로저
함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출(escape)한다고 표현한다. 클로저를 매개변수로 갖는 함수를 선언할 때 매개변수 이름의 콜론(:) 뒤에 @escaping 키워드를 사용하여 클로저가 탈출하는 것을 허용한다고 명시해줄 수 있다.
예를 들어 비동기 작업으로 함수가 종료되고 난 후 호출할 필요가 있는 클로저를 사용해야 할 때 탈출 클로저가 필요하다.
@escaping 키워드를 따로 명시하지 않는다면 매개변수로 사용되는 클로저는 기본으로 비탈출 클로저이다. 함수로 전달된 클로저가 함수의 동작이 끝난 후 사용할 필요가 없을 때 비탈출 클로저를 사용한다.
클로저가 함수를 탈출할 수 있는 경우 중 하나는 함수 외부에 정의된 변수나 상수에 저장되어 함수가 종료된 후에 사용할 경우다. 예를 들어 비동기로 작업을 하기 위해서 컴플리션 핸들러를 전달인자를 이용해 클로저 형태로 받는 함수들이 많다. 함수가 작업을 종료하고 난 이후(즉, 함수의 return 후)에 컴플리션 핸들러, 즉 클로저를 호출하기 때문에 클로저는 함수를 탈출해 있어야만 한다. 함수의 전달인자로 전달받은 클로절르 다시 반환할 때도 마찬가지다.
[코드 13-16]은 탈출 클로저와 그 클로저를 저장할 수 있는 함수 외부의 배열 변수가 있다.
/*
코드 13-16. 탈출 클로저를 매개변수로 갖는 함수
*/
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandler.append(completionHandler)
}
[코드 13-17]을 통해 탈출 클로저를 조금 더 자세히 살펴보자
/*
코드 13-17. 함수를 탈출하는 클로저의 예
*/
typealias VoidVoidClosure = () -> Void
let firstClosure: VoidVoidClosure = {
print("Closure A")
}
let secondClosure: VoidVoidClosure = {
print("Closure B")
}
// first와 second 매개변수 클로저는 함수의 반환 값으로 사용될 수 있으므로 탈출 클로저이다.
func returnOneClosure(first: @escaping VoidVoidClosure,
second: @escaping VoidVoidClosure, shouldReturnFirstClosure: Bool) -> VoidVoidClosure {
// 전달인자로 전달받은 클로저를 함수 외부로 다시 반환하기 때문에 함수를 탈출하는 클로저이다.
return shouldReturnFirstClosure ? first : second
}
// 함수에서 반환한 클로저가 함수 외부의 상수에 저장되었다.
let returnedClosure: VoidVoidClosure = returnOneClosure(first: firstClosure,
second: secondClosure, shouldReturnFirstClosure: true)
returnedClosure() // Closure A
var closures: [VoidVoidClosure] = []
// closure 매개변수 클로저는 함수 외부의 변수에 저장될 수 있으므로 탈출 클로저이다.
func appendClosure(closure: @escaping VoidVoidClosure) {
// 전달인자로 전달받은 클로저가 함수 외부의 변수 내부에 저장되므로 함수를 탈출한다.
closures.append(closure)
}
returnOneClosure 함수에서 두 파라미터 모두가 탈출할 수 있는 조건이 명확하기 때문에 @escaping 키워드를 사용하여 탈출 클로저임을 명시하지 않으면 컴파일 오류가 발생한다. 이 코드는 함수 외부로 다시 전달되어 외부에서 사용이 가능하다든가, 외부 변수에 저장되는 등 클로저의 탈출 조건을 모두 갖추고 있다.
타입 내부 메서드의 매개변수 클로저에 @escaping 키워드를 사용하여 탈출 클로저임을 명시한 경우, 클로저 내부에서 해당 타입의 프로퍼티나 메서드, 서브스크립트 등에 접근하려면 self 키워드를 명시적으로 사용해야 한다. 비탈출 클로저는 클로저 내부에서 타입 내부의 프로퍼티나 메서드, 서브스크립트 등에 접근할 때 self 키워드를 꼭 써주지 않아도 된다.
/*
코드 13-18. 클래스 인스턴스 메서드에 사용되는 탈출, 비탈출 클로저
*/
typealias VoidVoidClosure = () -> Void
func functionWithNoescapeClosure(closure: VoidVoidClosure) {
closure()
}
func functionWithEscapingClousre(completionHandler: @escaping VoidVoidClosure) -> VoidVoidClosure {
return completionHandler
}
class SomeClass {
var x = 10
func runNoescapeClosure() {
// 비탈출 클로저에서 self 키워드 사용은 선택 사항이다.
functionWithNoescapeClosure {
x = 200
}
}
func runEscapingClosure() -> VoidVoidClosure {
// 탈출 클로저에서는 명시적으로 self를 사용해야 한다.
return functionWithEscapingClousre {
self.x = 100
}
}
}
let instance: SomeClass = SomeClass()
instance.runNoescapeClosure()
print(instance.x) // 200
let returnedClosure: VoidVoidClosure = instance.runEscapingClosure()
returnedClosure()
print(instance.x) // 100
13.6.1 withoustActuallyEscaping
비탈출 클로저로 전달한 클로저가 탈출 클로저인 척 해야 하는 경우가 있다. 실제로는 탈출하지 않는데 다른 함수에서 탈출 클로저를 요구하는 상황에 해당한다. [코드 13-19]에 구현한 함수 hasElements(in:match:)는 in 매개변수로 검사할 배열을 전달받고, match라는 매개변수로 검사를 실행할 클로저를 받아들인다.
hasElements(in:match:) 함수는 @escaping 키워드가 없으므로 비탈출 클로저를 전달받게 된다. 그리고 내부에서 배열의 lazy 컬렉션에 있는 filter 메서드의 매개변수로 비탈출 클로저를 전달한다. 그런데 lazy 컬렉션은 비동기 작업을 할 때 사용하기 때문에 filter 메서드가 요구하는 클로저는 탈출 클로저이다. 그래서 탈출 클로저 자리에 비탈출 클로저를 전달할 수 없다는 오류를 마주하게 된다.
/*
코드 13-19. 오류가 발생하는 hasElements 함수
*/
func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
return (array.lazy.filter { predicate($0) }.isEmpty == false)
}
그런데 함수 전체를 보면, match 클로저가 탈출할 필요가 없다. 이때 해당 클로저를 탈출 클로저인양 사용할 수 있게 돕는 withoutActuallyEscaping(_:do:) 함수가 있다.
/*
코드 13-20. withoutActuallyEscaping(_:do:) 함수의 활용
*/
let numbers: [Int] = [2, 4, 6, 8]
let evenNumberPredicate = { (number: Int) -> Bool in
return number % 2 == 0
}
let oddNumberPredicate = { (number: Int) -> Bool in
return number % 2 == 1
}
func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
return withoutActuallyEscaping(predicate, do: { escapablePredicate in
return (array.lazy.filter { escapablePredicate($0) }.isEmpty == false)
})
}
let hasEvenNumber = hasElements(in: numbers, match: evenNumberPredicate)
let hasOddNumber = hasElements(in: numbers, match: oddNumberPredicate)
print(hasEvenNumber) // true
print(hasOddNumber) // false
withoutActuallyEscaping(_:do:) 함수의 첫 번째 전달인자로 탈출 클로저인 척해야 하는 클로저가 전달되었다. do 전달인자는 이 비탈출 클로저를 또 매개변수로 전달받아 실제로 작업을 실행할 탈출 클로저를 전달한다.
13.7 자동 클로저
함수의 전달인자로 전달하는 표현을 자동으로 변환해주는 클로저를 자동 클로저라고 한다. 자동 클로저는 전달인자를 갖지 않는다. 자동 클로저는 호출되었을 때 자신이 감싸고 있는 코드의 결괏값을 반환한다.
자동 클로저는 클로저가 호출되기 전까지 클로저 내부의 코드가 동작하지 않는다. 따라서 연산을 지연시킬 수 있다. 이 과정은 연산에 자원을 많이 소모한다거나 부작용이 우려될 때 유용하게 사용할 수 있다. 왜냐하면 코드의 실행을 제어하기 좋기 때문이다.
/*
코드 13-21. 클로저를 이용한 연산 지연
*/
// 대기 중인 손님들
var customerInLine: [String] = ["YoangWha", "SangYong", "SungHun", "HaMi"]
print(customerInLine.count) // 4
// 클로저를 만들어두면 클로저 내부의 코드를 미리 실행(연산)하지 않고 가지고만 있는다.
let customerProvider: () -> String = {
return customerInLine.removeFirst()
}
print(customerInLine.count) // 4
// 실제로 실행
print("Now serving \(customerProvider())!") // "Now serving YoangWha!"
print(customerInLine.count) // 3
customerProvider()를 선언했지만 클로저 내부의 연산이 반영되지 않아 다음 명령에서 4가 출력된다. 그 뒤에 실제로 클로저를 실행하게 되면 그때서야 연산을 실행하게 된다. 클로저가 영영 호출되지 않는다면 내부의 코드도 실행되지 않기 때문에 해당 연산은 실행되지 않는다.
[코드 13-22]와 같이 같은 조건의 클로저를 함수의 전달인자로 전달한다고 생각해보자.
/*
코드 13-22. 함수의 전달인자로 전달하는 클로저
*/
// customerInLine is ["YoangWha", "SangYong", "SungHun", "HaMi"]
var customerInLine: [String] = ["YoangWha", "SangYong", "SungHun", "HaMi"]
func serveCustomer(_ customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer( { customerInLine.removeFirst() } ) // "Now serving YoangWha!"
함수의 전달인자로 직접 클로저를 작성하여 전달해주었다.
[코드 13-23]은 [코드 13-22]를 자동 클로저를 사용하여 표현한 예다.
/*
코드 13-23. 자동 클로저의 사용
*/
// customerInLine is ["YoangWha", "SangYong", "SungHun", "HaMi"]
var customerInLine: [String] = ["YoangWha", "SangYong", "SungHun", "HaMi"]
func serveCustomer(_ customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer(customerInLine.removeFirst()) // "Now serving YoangWha!"
[코드 13-23]은 매개변수에 @autoclosure 속성을 주었기 때문에 자동 클로저 기능을 사용한다. 자동 클로저 속성을 부여한 매개변수는 클로저 대신에 customersInLine.removeFirst() 코드의 실행 결과인 String 타입의 문자열을 전달인자로 받게 된다. String 타입의 값이 자동 클로저 매개변수에 전달되면 String 값을 매개변수가 없는 String 값을 반환하는 클로저로 변환해준다. String 타입의 값을 전달 받는 이유는 자동 클로저의 반환 타입이 String이기 때문이다.
자동 클로저는 전달인자를 갖지 않기 때문에 반환 타입의 값이 자동 클로저의 매개변수로 전달되면 이를 클로저로 바꿔줄 수 있는 것이다.
자동 클로저를 사용하면 기존의 사용 방법처럼 클로저를 전달인자로 넘겨줄 수 없다.
기본적으로 @autoclosure 속성은 @noescape 속성을 포함한다. 만약 자동 클로저를 탈출하는 클로저로 사용하고 싶다면 @autoclosure 속성 뒤에 @escaping 속성을 덧붙여서 @autoclosure @escaping처럼 사용하면 된다.
/*
코드 13-24. 자동 클로저의 탈출
*/
var customersInLine: [String] = ["minjae", "innoceive", "sopress"]
func returnProvider(_ customerProvider: @autoclosure @escaping () -> String) -> (() -> String) {
return customerProvider
}
let customerProvider: () -> String = returnProvider(customersInLine.removeFirst())
print("Now serving \(customerProvider())!") // "Now serving minjae!"
'iOS > SWIFT' 카테고리의 다른 글
[Swift] Chapter 15. 맵, 필터, 리듀스 (0) | 2021.10.10 |
---|---|
[Swift] Chapter 14. 옵셔널 체이닝과 빠른 종료 (0) | 2021.10.09 |
[Swift] Chapter 12. 접근제어 (0) | 2021.10.08 |
[Swift] Chapter 11. 인스턴스 생성 및 소멸 (0) | 2021.10.08 |
[Swift] Chapter 10. 프로퍼티와 메서드 (0) | 2021.10.08 |
댓글