Closures Swift


Closures

클로져는 함수의 자체포함 블럭으로서  주위 것에 전달 가능하고 코드내에서 사용할 수 있다. 스위프트의 클로져는 C와 오브젝티브C내의 블록과 유사한 것이고 다른 프로그래밍 언어에서는 람다와 유사하다.

클로저는 이들이 정의된 컨텍스트에서 상수나 변수에 캡쳐하거나 참조로 저장될 수 있다. 이 것을 상수나 변수에 클로징이라고 한다. 스위프트는 캡쳐링되는 모든 메모리를 관리해준다.

일러두기)
캡쳐링의 개념에 대해 익숙치 않더라도 걱정할 필요 없다. 이 것은 아래의 값 캡쳐링에서 설명된다.

함수에서 소개된 글로벌과 네스트된 함수는 실제로는 클로져의 특별한 형태라 할 수 있다. 클로져는 세가지 형태를 갖는다.

- 글로벌 함수는 이름을 갖고 다른 변수를 캡쳐하지 않는 클로져이다.
- 네스트된 함수는 이름을 갖고 이 함수내의 값을 캡쳐할 수 있는 클로져이다.
- 클로져 표현식은 간단한 문법으로 쓰여진 이름없는 클로져로서 이를 감싸는 컨텍스트로부터의 값을 캡쳐할 수 있다.

스위프트의 클로져 표현은 클린, 클리어 스타일, 그리고 명료, 클러터 프리 문법을 가지며 다음 옵티마이제이션을 포함한다.
- 컨텍스트로 부터 파라미터와 반환 값 형식을 내포한다
- 단일 표현 클로져로부터 내포된 반환
- 짧은 매개변수 이름
- 테일링 클로져 문법

클로져 표현식

네스트된 함수에서 소개된 네스트된 함수는 더 큰 함수의 일부를 코드 블록으로 자체 포함되게 정의된 것으로 편리한 것이다. 그러나, 간혹 함수의 정의나 이름이 필요없이 함수와 같은 형태로 짧은 버전이 유용할 때가 있다. 이는 특히 하나 이상의 매개변수를 가진 함수를 취하는 함수나 메소드와 작업할 때 특히 그렇다.

클로져 표현식은 문법에 초점을 둔 간단하게 작성한 인라인 클로져를 작성하는 방법이다. 클로져 표현식은 몇몇 명확성과 인텐트를 잃지 않고 짧은 형태의 클로저를 작성하기 위한 방법을 제공한다. The closure expression examples below illustrate these optimizations by refining a single example of the sorted(by:) method over several iterations, each of which expresses the same functionality in a more succinct way.

소트 메소드

스위프트 표준 라이브러리는 sorted(by:) 라는 메소드를 제공한다. 이 것은 알려진 형식의 값들의 배열을 소트하는 것으로서 소팅 클로져의 결과값에 기반을 둔다. 일단 이 것이 소팅처리를 완료하면 sorted(by:) 메소드는 같은 형식과 사이즈의 새로운 배열을 반환한다. 원본 배열은 sorted(by:)메소드에 수정되지 않는다.

The closure expression examples below use the sorted(by:) method to sort an array of String values in reverse alphabetical order. Here’s the initial array to be sorted:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
The sorted(by:) method accepts a closure that takes two arguments of the same type as the array’s contents, and returns a Bool value to say whether the first value should appear before or after the second value once the values are sorted. The sorting closure needs to return true if the first value should appear before the second value, and false otherwise.

This example is sorting an array of String values, and so the sorting closure needs to be a function of type (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"]

If the first string (s1) is greater than the second string (s2), the backward(_:_:) function will return true, indicating that s1 should appear before s2 in the sorted array. For characters in strings, “greater than” means “appears later in the alphabet than”. This means that the letter "B" is “greater than” the letter "A", and the string "Tom" is greater than the string "Tim". This gives a reverse alphabetical sort, with "Barry" being placed before "Alex", and so on.

However, this is a rather long-winded way to write what is essentially a single-expression function (a > b). In this example, it would be preferable to write the sorting closure inline, using closure expression syntax.

클로져 표현 문법

클로져 표현 문법은 다음과 같은 일반 형식을 갖는다.
  1. { (parameters) -> return type in
  2. statements
  3. }

클로져 표현식 문법내의 파라미터는 인아웃  파라미터일 수 있지만 이들은 기본값을 가지지는 못한다. 가변 매개변수도 쓰여질 수 있다. 튜플도 매개변수나 반환값에 사용될 수 있다.

다음 예시는 backward(_:_:) 함수의 클로져 표현 버전을 보여준다.

reversedNames = names.sorted(by:{(s1:String,s2:String)->Bool in
  return s1>s2
})

이 인라인 클로져를 위한 파라미터와 반환형식의 정의는 backward(_:_:) 함수와 동일하다. In both cases, it is written as (s1: String, s2: String) -> Bool.  그러나, 인라인 클로져 표현식에서는 매개변수와 반환 형식은 컬리 브레이스 내에 쓰여졌다.

클로져의 바디의 시작은 in 키워드로 시작된다. 이 키워드는 클로져의 파라미터와 반환값이 끝나고 클로져의 바디가 시작함을 나타낸다.

클로져의 바디가 짧기 때문에 단일 라인으로도 쓰일 수 있다.

reversedNames = names.sorted(by:{(s1:String,s2:String)->Bool in return s1 > s2 })

This illustrates that the overall call to the sorted(by:) method has remained the same. A pair of parentheses still wrap the entire argument for the method. However, that argument is now an inline closure.

컨텍스트로부터 형식 내포

소팅 클로져가 메소드로의 매개변수로 건내지는 것이므로 스위프트는 이 파라미터의 형식과 그 반환하는 값의 형식으로 인퍼할 수 있다. sorted(by:)메소드는 문자열의 배열상에 호출되는 것으로서 이 매개변수는 형식 (String, String) -> Bool 의 함수형식이어야 한다. 이 것은 (String, String) 과 Bool 형식은 클로져 표현식의 정의에 쓸 필요가 없다는 뜻이다. 모든 형식이 인퍼가능하므로 반환 화살표 (->) 와 파라미터의 이름주위의 괄호도 생략가능하다.

reversedNames = names.sorted(by: {s1,s2 in return s1 > s2})

그럼에도, 원한다면 명시적으로 형식을 사용할 수 있다. 이를 통해 모호성을 제거하기 위한 것일 수도 있다. In the case of the sorted(by:) method, the purpose of the closure is clear from the fact that sorting is taking place, and it is safe for a reader to assume that the closure is likely to be working with String values, because it is assisting with the sorting of an array of strings.

단일 익스프레션 클로져로부터 내포 반환

Single-expression closures can implicitly return the result of their single expression by omitting the return keyword from their declaration, as in this version of the previous example:

reversedNames = names.sorted(by:{s1,s2 in s1 > s2})

sorted(by:) 메소드의 매개변수의 함수형식은 Bool 형식이 반드시 클로져에의해 반환됨이 명확하다. 클로져의 바디는 단일 표현식인 (s1 > s2) 를 포함하고 Bool 값을 반환한다. 모호성이 없으므로 return 키워드는 생략가능하다

짧은 매개변수 이름

스위프트는 자동적으로 인라인 클로져로 짧은 매개변수 이름을 제공하는데, 클로져의 매개변수의 값으로 $0, $1, $2 .. 등으로 참조될 수 있다.

클로져 표현식내에서 이들을 사용하면 정의로부터 매개변수 리스트를 생략하고 짧은 매개변수이름은 기대하는 함수형식으로부터 참조될 수 있게된다. in 키워드도 생략가능해지는데 클로져 표현식이 완전히 이 바디로만 만들어지기 때문이다.

reversedNames = names.sorted(by:{$0 > $1})

이 $0, $1 은 클로저의 첫 번째 그리고 두번째 문자열 매개변수이다.

연산자 메소드

위의 클로져 표현식보다 더 짧은 방법도 있다. 스위프트의 무자열 형식은 > 연산자의 구현에서 스트링 특정 구현을 정의하는데 스트링의 두 파라미터를 갖고 Bool 을 반환한다. 이 것은 sorted(by:) 메소드가 요구하는 메소드 형식과 정확히 일치한다. 그러므로, 이 연산자만을 전달하면 스위프트는 원하는 구현을 인퍼할 수 있다.

reversedNames = names.sorted(by:>)

For more about operator method, see Operator Methods.

트레일링 클로져

클로져 표현식을 함수의 최종 매개변수로 보내고 클로져 표현이 길면 트레일링 클로져를 쓰는게 유용하다. 트레일링 클로져는 함수 호출의 괄호 이후에 쓰여지는 것인데도 여전히 함수의 메개변수이다. 트레일링 클로져 문법을 사용할 때 함수 호출의 일부에서 클로져를 위한 매개변수 레이블은 쓰지 않는다.

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 }

트레일링 클로져는 클로저가 꽤 길고 단일 라인에 인라인하기 어려울 때 특히 유용하다. 예를 들어, 스위프트의 배열 형식은 map(_:)메소드를 갖는다. 클로져 표현식을 단일 매개변수로 취한다. 클로져는 배열에서 한번씩 호출되고 결과는 대체되는 맵된 값이다. (다른 형식이 될 수도 있음) 매핑의 형태와 반환된 값의 형식은 지정할 클로져에서 남은 모든것이다.

After applying the provided closure to each array element, the map(_:) method returns a new array containing all of the new mapped values, in the same order as their corresponding values in the original array.

Here’s how you can use the map(_:) method with a trailing closure to convert an array of Int values into an array of String values. The array [16, 58, 510] is used to create the new array ["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]
The code above creates a dictionary of mappings between the integer digits and English-language versions of their names. It also defines an array of integers, ready to be converted into strings.

You can now use the numbers array to create an array of String values, by passing a closure expression to the array’s map(_:) method as a trailing closure:

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 파라미터로 초기화된다. 그러므로 값은 클로져 바디내에서 변경될 수 있다. (함수나 클로져로의 파라미터는 항상 상수이다.) 클로져 표현식은 String 의 반환형식을 지정하는데 형식을 가리히는 것으로 출력 배열내에 저장된다.

클로져 표현식은 output 이라 불리는 문자열을 생성한다. 나머지 연산자를 사용하여 숫자의 마지막 디지트를 계산하며, 이 디지트를 digitNames 딕셔너리내에서 적절한 문자열을 찾는데 사용한다. 이 클로져는 0보다 큰 정수에 대한 문자 표현을 생성하는데 사용된다.

NOTE

The call to the digitNames dictionary’s subscript is followed by an exclamation mark (!), because dictionary subscripts return an optional value to indicate that the dictionary lookup can fail if the key does not exist. In the example above, it is guaranteed that number % 10 will always be a valid subscript key for the digitNames dictionary, and so an exclamation mark is used to force-unwrap the String value stored in the subscript’s optional return value.

digitNames 딕셔너리로부터 얻은 문자열은 output 의 앞에 더해지며 반대로 숫자의 문자열 버전을 생성한다. (The expression number % 10 gives a value of 6 for 16, 8 for 58, and 0 for 510.)

The number variable is then divided by 10. Because it is an integer, it is rounded down during the division, so 16 becomes 1, 58 becomes 5, and 510 becomes 51.

The process is repeated until number is equal to 0, at which point the output string is returned by the closure, and is added to the output array by the map(_:) method.

The use of trailing closure syntax in the example above neatly encapsulates the closure’s functionality immediately after the function that closure supports, without needing to wrap the entire closure within the map(_:) method’s outer parentheses.

값 캡쳐링

클로져는 정의된 컨텍스트를 둘러싼 곳에서 상수와 변수를 캡쳐할 수 있다. 클로져는 이 바디 내에서 또는 원본 범위의 상수나 변수가 더이상 존지하지 않더라도 상수나 변수의 값을 참조하고 변경할 수 있다.

스위프트에서, 값을 캡쳐할 수 있는 클로져의 가장 단순한 형식은 네스트드 함수로서 다른 함수의 바디 내에 쓰인다. 네스트드 함수는 외부 함수의 매개변수를 비롯한 모든 값을 캡쳐할 수 있다.

여기의 예시는 makeIncrementer 라는 함수로서 incrementer 라는 네스트드 함수를 포함한다. incrementer() 함수는 두 값을 캡쳐하는데 runningTotal, amount 이다. 이들을 캡쳐해 incrementer 는 makeIncrementr를 통해 호출할 때마다 amount 양만큼 runningTotal을 증가시키는 클로져를 반환한다.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
  var runningTotal = 0
  func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
  }
  return incrementer
}

makeIncrementer의 반환형식은 () -> Int 이다. 이 뜻은 단순 값이 아닌 함수라는 뜻이다. 반환하는 함수는 파라미터는 없고 각 호출마다 Int를 반환한다.To learn how functions can return other functions, see Function Types as Return Types.

makeIncrementer(forIncrement:) 함수는 runningTotal 이라는 변수를 정의하고 인크레멘터의 반환될 현 러닝 토탈을 저장한다. 이 변수는 0으로 초기화된다.

makeIncrementer(forIncrement:) 함수는 forIncrement 로 매개변수 레이블된 단일 Int 파라미터와 amount 라는 파라미터를 갖는다.The argument value passed to this parameter specifies how much runningTotal should be incremented by each time the returned incrementer function is called. The makeIncrementer function defines a nested function called incrementer, which performs the actual incrementing. This function simply adds amount to runningTotal, and returns the result.

When considered in isolation, the nested incrementer() function might seem unusual:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}
The incrementer() function doesn’t have any parameters, and yet it refers to runningTotal and amount from within its function body. It does this by capturing a reference to runningTotal and amount from the surrounding function and using them within its own function body. Capturing by reference ensures that runningTotal and amount do not disappear when the call to makeIncrementer ends, and also ensures that runningTotal is available the next time the incrementer function is called.

일러두기)
옵티마이제이션으로 스위프트는 클로져에의해 변경되지 않는 값고 클로져가 실행된 후 값이 변경되지 않으면 캡쳐하는 대신 값을 복사한다.
스위프트는 또한 이들이 더이상 필요치 않을 때 변수를 디스포징하는 모든 메모리 관리를 수행한다.

Here’s an example of makeIncrementer in action:

let incrementByTen = makeIncrementer(forIncrement: 10)
This example sets a constant called incrementByTen to refer to an incrementer function that adds 10 to its runningTotal variable each time it is called. Calling the function multiple times shows this behavior in action:

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

만약 두번째 incrementer를 생성하면 새로운 구별된 runningTotal 변수를 가진다.

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7
Calling the original incrementer (incrementByTen) again continues to increment its own runningTotal variable, and does not affect the variable captured by incrementBySeven:

incrementByTen()
// returns a value of 40

일러두기)
만약 클래스 인스턴스의 프로퍼티로 클로저를 할당하고 그 클로저가 인스턴스 또는 멤버를 참조하는 인스턴스를 캡쳐하면 클로져와 인스턴스 사이의 스트롱 참조 사이클을 생성한다. 스위프트는 캡쳐 리스트를 사용해 이들 스트롱 리퍼런스 사이클을 끊는다.For more information, see Strong Reference Cycles for Closures.

클로져는 참조형식이다

위의 예시에서 incrementBySeven 과 incrementByTen 은 상수이지만, 이들 상수를  참조하는 클로저는 여전히 캡쳐한 runningTotal 을 증가시킬 수 있다. 이 것은 함수와 클로저가 참조형식이기 때문이다.

함수 또는 클로저를 상수나 변수에 할당하면 그 상수나 변수를 함수나 클로져로 참조하기 위한 실제적 설정을 하는 것이다. 위의 예시에서 incrementByTen 상수로 참조하는 것이지 클로저 자체의 컨텐트는 아니다.

이는 또한 만약 클로져를 두개의 다른 상수 또는 변수에 할당하면 이들 상수 또는 변수는 같은 클로저를 참조하게 됨을 의미한다.

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

incrementByTen()
// returns a value of 60

The example above shows that calling alsoIncrementByTen is the same as calling incrementByTen. Because both of them refer to the same closure, they both increment and return the same running total.

클로져에서 탈출하기

클로저는 클로저가 함수로 매개변수형식으로 전달 되지만 함수 반환이후 호출될 때 함수를 탈출한다라고 한다. 클로저를 매개변수로받지만 클로저가 탈출할 수 있음을 허용하는 것을 지정하기 위해 파라미터 형식 앞에 @escaping 을 쓸수 있다.

클로져가 탈출할 방법은 함수 바깥에서 정의된 변수에 저장하는 것이다. 예로들어, 많은 함수들이 비동기적 연산을 완료 핸들러로 클로져 매개변수를 받아 시작한다. 함수는 이 것이 연산을 한 후에 실행하지만, 클로져는 해당 연산이 완료되기 전에는 호출되지 않는다. 클로져가 이후에 호출되려면 탈출할 필요가 있다. 예를 들어:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
  completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:) 함수는 클로져르 매개변수로 받고 함수 외부에 정의된 배열에 더한다. 만약 @escaping 으로 함수의 파라미터를 지정하지 않으면 컴파일 에러가 발생한다.

@escaping 으로 지정한 클로져는 클로저 내에서 self 를 명시적으로 지정해야 함을 의미한다. 예를들어, 아래 코드에서 someFunctionWithEscapingClosure(_:) 에 전달되는 클로져는 탈출 클로져로서 self를 명시적으로 참조해야 함을 의미한다. 대조적으로 someFunctionWithNonescapingClosure(_:)은 비탈출 클로져로서 self는 내포적으로 참조될 수 있다.

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"

오토클로져

오토클로져는 함수로 매개변수로 전달되는 표현식을 감싸기 위해 자동적으로 생성되는 클로져이다. 이 것은 호출될 때 어떤 매개변수도 없고 이 내부에서 감싸지는 표현식의 값을 반환한다. 이 문법적 잇점은 명시적 클로져 대신 일반 표현식을 작성함으로서 함수의 파라미터 주위의 브레이스를 생략할 수 있게 해준다.

이 것은 호출할 함수는 오토클로져를 받는게 보통이지만 함수의 형식을 구현하는 데에서는 일반적이지 않다. 예를 들어, assert(condition:message:file:line:) 함수는 컨디션과 메시지 파라미터로는 오토클로져를 받지만, 컨디션 파라미터는 디버그 빌드에서만 실행되는 파라미터이며 메시지 파라미터는 오직 컨디션이 거짓일때만 실행된다.

오토클로져는 실행에 딜레이를 줄 수 있는데 내부 코드는 클로져를 호출하기 전까지 실행되지 않기 때문이다. 지연된 실행은 사이드 이펙트를 갖거나 계산적으로 비싼경우 유용한데 코드가 실행되는 것을 제어할 수 있기 때문이다. 아래 코드는 어떻게 클로져 실행을 지연하는지 보여준다.

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"

클로져 내 코드를 통해 첫 엘리먼트가 삭제되지만, 실제로 클로져가 호출되기 전까지 배열 엘리먼트는 제거되지 않는다. 만약 클로저가 호출되지 않으면 클로저 내부는 절대 실행되지 않을 것이고, 배열 엘리먼트는 절대 삭제되지 않을 것이다. Note that the type of customerProvider is not String but () -> String—a function with no parameters that returns a 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 속성을 통해 매개변수 형식을 마크하여 오토클로져를 받는다. 이제 클로져 대신에 문자열 매개변수를 받게 함수를 호출할 수 있다. 매개변수는 자동적으로 클로져로 전환되는데 customerProvider 매개변수의 형식은 @autoclosure 속성으로 마크되어 있기 때문이다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
  print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at:0)

일러두기) 오토클로져를 너무 많이 사용하면 코드를 이해하기 어렵게 된다. 컨텍스트와 함수이름은 실행이 디퍼됨을 알수있게 해야한다.

탈출하는 것이 허용된 오토클로져를 원하면 @autoclosure 와 @escaping 속성을 사용한다. The @escaping attribute is described above in 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!"

In the code above, instead of calling the closure passed to it as its customerProvider argument, the collectCustomerProviders(_:) function appends the closure to the customerProviders array. The array is declared outside the scope of the function, which means the closures in the array can be executed after the function returns. As a result, the value of the customerProvider argument must be allowed to escape the function’s scope.


덧글

댓글 입력 영역