[ 이 게시물은 Swift.org를 참고하며 Swift를 공부하기 위해 작성하는 글 입니다. ]
Swift의 클로저 (Closures)는 다른 프로그래밍 언어에서 클로저, 익명 함수, 람다, 블록과 유사합니다.
클로저는 정의된 컨텍스트에서 모든 상수와 변수에 대한 참조를 캡처하고 저장할 수 있습니다. 이러한 상수와 변수를 폐쇄 (closing over)라고 합니다. Swift는 캡처의 모든 메모리 관리를 처리합니다.
클로저는 3가지 형태 중 하나를 취합니다.
- 전역 함수는 이름을 가지고 어떠한 값도 캡처하지 않는 클로저입니다.
- 중첩 함수는 이름을 가지고 둘러싼 함수로 부터 값을 캡처할 수 있는 클로저입니다.
- 클로저 표현식은 주변 컨텍스트에서 값을 캡처할 수 있는 경량 구문으로 작성된 이름이 없는 클로저입니다.
Swift의 클로저 표현식은 일반 시나리오에서 간단하고 깔끔한 구문을 위한 최적화를 통해 깔끔하고 명확한 스타일을 가지고 있습니다. 이러한 최적화는 아래와 같은 것들이 포함됩니다.
- 컨텍스트에서 파라미터와 반환값 타입 유추
- 단일 표현식 클로저의 암시적 반환
- 약식 인수 이름
- 후행 클로저 구문
클로저 표현식 (Closure Expressions)
클로저 표현식 (Closure expressions)은 짧은 형태로 클로저를 작성하기 위한 몇개의 구문 최적화를 제공합니다. 아래의 클로저 표현식 예제는 여러 반복에 걸쳐 sorted(by:) 메소드의 단일 예제를 구체화하는 최적화 예시입니다. 각 예시는 동일한 기능을 보다 간결하게 표현합니다.
정렬 메소드 (The Sorted Method)
Swift의 표준 라이브러리는 사용자가 제공하는 정렬 클로저의 출력을 기반으로 타입의 값 배열을 정렬하는 sorted(by:)라는 정렬 메소드를 제공합니다. 정렬 프로세스가 완료되면 sorted(by:) 메소드는 원본 배열과 같은 타입과 같은 크기의 올바르게 정렬된 새로운 배열을 반환합니다. 기존 배열은 sorted(by:) 메소드로 수정되지 않습니다.
아래 예시들의 클로저 표현식은 알파벳 역순으로 String 값의 배열을 정렬하기 위해 sorted(by:) 메소드를 사용합니다. 아래 배열은 정렬하기 전의 배열입니다.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by:) 메소드는 값이 정렬된 후 첫번째 값이 두번째 값의 앞 또는 뒤에 표시되어야 하는지 여부를 나타내는 Bool 값을 반환합니다. 정렬 클로저는 첫번째 값이 두번째 값 앞에 나타나야 하는 경우 true를 반환하고 그렇지 않으면 false를 반환합니다.
아래 예시는 String 값의 배열을 정렬하고 정렬 클로저는 (String, String) -> Bool 타입의 함수를 필요로 합니다.
정렬 클로저를 제공하는 한가지 방법은 올바른 타입의 일반 함수를 작성하고 sorted(by:) 메소드에 인수로 전달하는 것입니다.
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
위 예시에서 backward(_:_:) 함수는 첫번째 문자열 (s1)이 두번째 문자열(s2)보다 크다면 true를 반환합니다. 문자열의 문자가 "더 크다"는 "알파벳 순으로 더 뒤에 나타난다"는 의미입니다. 이것은 문자 B는 문자 A보다 "더 크다"이고 문자열 "Tom"은 문자열 "Tim"보다 더 큽니다.
그러나 위 예시는 단일 표현식 함수 (a > b)를 작성하는 방법 중 다소 긴 방식입니다. 이 예시에서는 클로저 표현식 구문을 사용하여 정렬 클로저를 인라인으로 작성하는 것이 좋습니다.
클로저 표현구 (Closure Expresssion Syntax)
클로저 표현구는 아래와 같은 형태를 가지고 있습니다.
{ (<#parameters#>) -> <#return type#> in
<#statements#>
}
클로저 표현구의 파라미터는 in-out 파라미터일 수도 있지만, 기본값을 가질 수 없습니다. 가변 파라미터의 이름을 지정하면 가변 파라미터를 사용할 수 있습니다. 튜플은 파라미터 타입과 반환 타입으로 사용될 수도 있습니다.
아래 예시는 위에서 backward(_:_:) 함수의 클로저 표현 버전입니다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
이 인라인 클로저의 파라미터와 반환 타입은 위의 backward(_:_:) 함수에서 선언한 것과 동일합니다. 두 경우는 모두 (s1: String, s2: String) -> Bool로 작성합니다. 그러나 인라인 클로저 표현식을 위한 파라미터와 반환 타입은 중괄호 바깥이 아닌 안에 작성합니다.
클로저 본문의 시작은 in 키워드로 시작합니다. 이 키워드는 클로저의 파라미터와 리턴 타입 정의가 끝났음을 나타내며 클로저의 본문이 시작함을 나타냅니다.
클로저의 본문이 너무 짧기 때문에 아래와 같이 한줄로 작성할 수도 있습니다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
이는 sorted(by:) 메소드에 대한 전체 호출이 동일하게 유지되었음을 보여줍니다. 소괄호는 여전히 메소드의 전체 인자를 둘러싸고 있지만, 인자는 인라인 클로저입니다.
컨텍스트로 타입 추론 (Inferring Type From Context)
정렬 클로저는 메소드에 인수로 전달되기 때문에 Swift는 파라미터 타입과 반환되는 값의 타입을 추론할 수 있습니다. sorted(by:) 메소드는 문자열 배열에서 호출되므로 인수는 (String, String) -> Bool 타입의 함수여야 합니다. 이는 (String, String)과 Bool 타입을 클로저 표현식 정의에 일부러 작성할 필요가 없음을 의미합니다. 모든 타입은 유추할 수 있기 때문에 반환 화살표 (->)와 파라미터의 이름을 둘러싼 소괄호를 생략할 수 있습니다.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
함수나 메서드에 클로저를 인라인 클로저 표현식으로 전달할 때 항상 파라미터 타입과 반환 타입을 유추할 수 있으므로, 결과적으로 클로저가 함수 또는 메소드를 인자로 사용될 때 완전한 형태의 인라인 클로저를 작성할 필요가 없습니다.
후행 클로저 (Trailing Closures)
함수의 마지막 인수로 함수에 클로저 표현식을 전달해야하고 클로저 표현식이 긴 경우 후행 클로저 (trailing closure)로 작성하는 것이 유용할 수 있습니다. 후행 클로저는 함수의 인수이지만 함수 호출의 소괄호 다음에 작성합니다. 후행 클로저 구문을 사용할 때 함수 호출 시 클로저 인수 라벨을 작성하지 않아도 됩니다.
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
위의 클로저 표현구 섹션의 문자열 정렬 클로저는 후행 클로저로 sorted(by:) 메소드의 소괄호 바깥에 작성될 수 있습니다.
reversedNames = names.sorted() { $0 > $1 }
후행 클로저로 표현식이 함수와 메소드의 유일한 인수일 경우 함수를 호출할 때 함수 또는 메소드 이름 뒤에 소괄호 ()를 작성하지 안하도 됩니다.
reversedNames = names.sorted { $0 > $1 }
후행 클로저는 클로저가 길어서 한줄로 인라인으로 작성이 불가능할 때 유용합니다. 예를 들어 Swift의 Array 타입은 단일 인수로 클로저 표현식을 가지는 map(_:) 메소드가 있습니다. 이 클로저는 배열의 각 아이템에 대해 한번 호출되고 아이템에 대해 매핑된 대체값 (다른 타입일 수 있음)이 반환됩니다. map(_:) 에 전달한 클로저에 작성된 코드에 따라 매핑 특성과 반환된 값의 타입을 지정합니다.
제공된 클로저에 각 배열의 요소를 적용한 후에 map(_:) 메소드는 기존 배열에 해당값과 같은 순서로 새로 매핑된 값의 새로운 배열을 반환합니다.
아래 예시는 Int 값의 배열을 String 값의 배열로 변환하기 위해 후행 클로저와 map(_:) 메소드를 어떻게 사용하는지 보여줍니다. 배열 [16, 58, 510]은 새로운 배열 ["OneSix", "FiveEight", "FiveOneZero"]을 생성하는데 사용됩니다.
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
위 코드는 정수와 그 정수에 맞는 영어 표기를 매핑하는 딕셔너리를 생성합니다. 문자열로 변환하기 위한 정수으 배열을 의미합니다. numbers 배열을 사용하여 후행 클로저로 map(_:) 메소드로 클로저 표현식을 전달하여 String 값의 배열을 생성할 수 있습니다.
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
map(_:) 메소드는 배열에 각 아이템을 위한 클로저 표현식을 호출합니다. 매핑할 배열의 값에서 유추할 수 있으므로 클로저의 입력 파라미터인 number 타입을 지정할 필요가 없습니다.
이 예시에서 변수 number는 클로저의 number 파라미터의 값으로 초기화되기 때문에 값은 클로저 본문 내에서 수정될 수 있습니다. 클로저 표현식은 출력 매핑된 출력 배열에 저장될 타입을 나타내기 위해 String 타입도 변환 타입을 지정합니다.
클로저 표현식은 호출될 때마다 output 이라는 문자열을 만듭니다. 나머지 연산자(number % 10)를 이용하여 number의 마지막 숫자를 계산하고 digitNames 딕셔너리에 적절한 숫자 문자열을 찾습니다. 클로저는 0보다 큰 정수에 대한 문자열 표현을 생성하는데 사용할 수 있습니다.
위의 예시처럼 후행 클로저 구문을 사용하면 클로저가 지원하는 함수 바로 뒤에 있는 클로저의 기능을 깔끔하게 캡슐화 합니다. 전체 클로저를 map(_:) 메소드의 바깥 소괄호로 감쌀 필요가 없습니다.
함수가 여러개의 클로저를 가지고 있다면 첫번째 후행 클로저의 인수 라벨을 생략하고 남은 후행 클로저의 라벨은 표기합니다. 예를 들어 아래의 함수는 사진 갤러리에서 사진 하나를 불러옵니다.
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
하나의 사진을 불러오기 위해 이 함수를 호출할 때 2개의 클로저를 제공합니다. 첫번째 클로저는 사진 다운로드 완료후에 사진을 보여주기 위한 완료 처리입니다. 두번째 클로저는 오류를 표시하는 오류 처리기 입니다.
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
이러한 방식으로 함수를 작성하면 두 상황을 모두 처리하는 하나의 클로저를 사용하는 대신에, 다운로드 성공 후 사용자 인터페이스를 업데이트 하는 코드와 네트워크 오류를 처리하는 코드를 명확하게 분리할 수 있습니다.
캡처값 (Capturing Values)
클로저는 정의된 둘러싸인 컨텍스트에서 상수와 변수를 캡처 (capture) 할 수 있습니다. 그러면 클로저는 상수와 변수를 정의한 원래 범위가 더이상 존재하지 않더라도 본문 내에서 해당 상수와 변수의 값을 참조하고 수정할 수 있습니다.
Swift에서 값을 캡쳐할 수 있는 가장 간단한 클로저 형태는 다른 함수의 본문 내에 작성하는 중첩 함수입니다. 중첩 함수는 바깥 함수의 어떤 인수든 캡처할 수 있고 바깥 함수에서 정의된 상수와 변수를 캡쳐할 수도 있습니다.
아래는 incrementer 라는 중첩 함수가 포함된 makeIncrementer 라는 함수의 예시입니다. incrementer() 중첩 함수는 둘러싸인 컨텍스트에 runningTotal과 amount 2개의 값을 캡처합니다. 이 값을 캡처한 후에 incrementer는 호출될 때마다 amount로 runningTotal을 증가시키는 클로저로 makeIncrementer에 의해 반환됩니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer의 반환 타입은 () -> Int 입니다. 이는 값이 아닌 함수를 반환한다는 의미입니다. 반환하는 함수에는 파라미터가 없으며 호출될 때마다 Int 값을 반환합니다.
makeIncrementer(forIncrement:) 함수는 반환될 현재 증가분을 저장하기 위해 runningTotal 이라는 정수 변수를 정의합니다. 이 변수는 0으로 초기화합니다.
makeIncrementer(forIncrement:) 함수는 forIncrement 인수 라벨의 하나의 Int 파라미터 amonut 라는 이름의 파라미터를 가지고 있습니다. 이 파라미터에 전달된 인수값은 반환된 증가 함수가 호출 될 때마다 runningTotal을 얼마나 증가시켜야 하는지를 의미합니다. makeIncrementer 함수는 실제 증가를 수행하는 incrementer 라는 중첩 함수를 정의합니다. 이 함수는 간단하게 amount 를 runningTotal에 더하고 그 결과를 반환합니다.
incrementer() 함수는 단독으로 사용된다고 생각하면 비정상적으로 보일 수 있습니다.
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
왜냐하면, incrementer() 함수는 함수 본문 내의 runningTotal과 amount를 참조하고 있기 때문에 파라미터가 없습니다. 둘러싸인 함수에 runningTotal과 ammount에 대한 참조(reference)를 캡처하고 함수 내에서 사용합니다. 참조를 캡처하는 것 덕분에 makeIncrementer 호출이 종료될 때 runningTotal과 amount가 사라지지 않고 다음에 incrementer 함수가 호출될 때 runningTotal을 사용할 수 있습니다.
아래는 makeIncrementer 동작의 예시입니다.
let incrementByTen = makeIncrementer(forIncrement: 10)
이 예시에서는 호출될 때마다 runningTotal 변수에 10을 더하는 증가 함수를 참조하도록 incrementByTen 이라는 상수를 설정했습니다. 함수를 여러번 호출될 때 마다 runningTotal이 증가되는 동작을 볼 수 있습니다.
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
클로저는 참조 타입이다 (Closures Are Reference Types)
위 예제에서 incrementByTen은 상수이지만 이러한 상수가 참조하는 클로저는 캡처한 runningTotal 변수를 계속 증가시킬 수 있습니다. 이는 함수와 클로저가 참조 타입 (reference types) 이기 때문입니다.
함수 또는 클로저를 상수 또는 변수에 할당할 때마다 실제로 해당 상수 또는 변수를 함수 또는 클로저에 대한 참조로 설정합니다. 위의 예시에서 incrementByTen은 클로저 자체의 내용이 아니라 상수를 가리키는 클로저입니다.
이는 또한 서로 다른 2개의 상수 또는 변수에 클로저를 할당한다면 2개의 상수 또는 변수는 모두 같은 클로저를 참조한다는 의미입니다.
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
incrementByTen()
// returns a value of 60
위의 예제는 alsoIncrementByTen 호출은 incrementByTen 호출과 같음을 의미합니다. 2개 모두 같은 클로저를 참조하기 때문에 둘다 똑같이 runningTotal을 증가시키고 반환합니다.
탈출 클로저 (Escaping Closures)
함수의 인수로 클로저를 전달하지만 함수가 반환된 후 호출되는 클로저를 함수 탈출 (escape) 이라고 말합니다. 클로저를 파라미터로 갖는 함수를 선언할 때 이 클로저는 탈출을 허락한다는 의미로 파라미터의 타입 전에 @escaping 을 작성할 수 있습니다.
클로저가 탈출할 수 있는 한가지 방법은 함수 바깥에 정의된 변수에 저장하는 것입니다. 예를 들어, 비동기 작업을 시작하는 대분의 함수는 완료 핸들러로 클로저를 사용합니다. 이 함수는 작업을 시작한 후에 반환되지만, 작업이 완료될때까지 클로저가 호출되지 않습니다. 클로저가 호출되기 위해서는 먼저 탈출해야 합니다. 예를 들면 아래와 같습니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:) 함수는 매개변수로 클로저를 가지고 있고 함수 바깥에 선언된 배열을 추가합니다. 함수의 파라미터에 @escaping 을 표시하지 않으면 컴파일 시 에러가 발생합니다.
self 를 참조하는 이스케이프 클로저는 self 가 클래스의 인스턴스를 참조하는 경우 구현에 대한 추가적인 고민이 필요합니다. 이스케이프 클로저에 self 캡쳐를 하는 경우에는 강한 참조 사이클이 생기기 쉽습니다.
일반적으로 클로저는 클로저 내부에서 변수를 사용하여 암시적으로 변수를 캡처하지만 이 경우에는 명시적으로 캡처해야 합니다. self를 캡처하려면 사용할 때 명시적으로 self를 작성하거나 클로저의 캡처 리스트에 self를 포함해야 합니다. self를 명시적으로 작성해서 의도를 표현하고 참조 사이클이 없음을 확인시켜주어야 합니다.
예를 들어, 아래 코드에서 someFunctionWithEscapingClosure(_:)에 전달된 클로저는 명시적으로 self를 참조합니다. 반대로 someFunctionWithNonescapingClosure(_:)에 전달된 클로저는 바이스케이프 클로저입니다. 즉, 암시적으로 self를 참조할 수 있습니다.
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
아래의 doSomething() 메소드는 클로저의 캡처 리스트에 self를 캡처하고 암시적으로 self를 참조합니다.
class SomeOtherClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { [self] in x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
self가 구조체 또는 열거형 인스턴스이면 항상 암시적으로 self를 참조할 수 있습니다. 하지만, 이스케이프 클로저는 구조체 또는 열거형 인스턴스이면 self에 대한 변경 가능한 참조를 캡처할 수 없습니다. 구조체와 열거형은 공유 변경을 허용하지 않습니다.
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
위의 예제에서 someFunctionWithEscapingClosure 함수 호출은 변경 가능한 메소드 내부에 있기 때문에 에러이고, self는 변경 가능합니다. 이는 이스케이프 클로저는 구조체인 self를 변경가능한 참조로 캡처할 수 없다는 규칙을 위반합니다.
자동 클로저 (Autoclosures)
자동 클로저 (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"
클로저 내부의 코드에 의해 customersIsLine 배열의 첫번째 요소는 삭제되지만 클로저가 실제로 호출되기 전까지 삭제되지 않습니다. 클로저가 호출되지 않으면 클로저 내부의 표현식은 판단되지 않습니다. 이것은 배열의 요소가 삭제되지 않는다는 의미입니다. 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 속성으로 표시되는 인수는 자동으로 클로저로 변환됩니다.
// 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!"
자동 클로저가 이스케이프를 허용하길 원한다면 @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!"
위의 코드에서 customerProvider 인수로 전달된 클로저를 호출하는 대신에 collectCustomerProviders(_:) 함수는 클로저를 customerProviders 배열에 추가합니다. 이 배열은 함수의 범위 밖에 선언됩니다. 이는 배열 클로저는 함수가 반환된 후에야 실행될 수 있다는 의미입니다. 그 결과 customerProvider 인수의 값은 함수의 범위를 벗어날 수 있어야 합니다.
References
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/
Documentation
docs.swift.org