클로져 (Closures)

클로저는 코드에서 전달 및 사용할 수 있는 자체 포함 된 기능 블록입니다. Swift의 클로저는 C 및 Objective-C의 블록과 다른 프로그래밍 언어의 람다와 유사합니다.

클로저는 정의된 컨텍스트에서 모든 상수 및 변수에 대한 참조캡처하고 저장할 수 있습니다. 이것은 그 상수와 변수를 닫는 것으로 알려져 있습니다.. Swift는 캡처(capturing)의 모든 메모리 관리를 처리합니다.

NOTE
캡처 개념에 익숙하지 않아도 걱정하지 마십시오. 아래 값 캡처에서 자세히 설명합니다.

함수에 소개된 전역 함수와 중첩 함수는 실제로 특수한 경우의 클로저입니다. 폐쇄는 세 가지 형태 중 하나를 취합니다.

  • 전역 함수는 이름이 있고 값을 캡처하지 않는 클로저입니다.
  • 중첩 함수는 이름이 있고 둘러싸는 함수에서 값을 캡처 할 수있는 클로저입니다.
  • 클로저 표현식은 주변 컨텍스트에서 값을 캡처 할 수 있는 경량 구문으로 작성된 명명되지 않은 클로저입니다.

Swift의 클로저 표현식은 일반적인 시나리오에서 간단하고 깔끔한 구문을 장려하는 최적화를 통해 깔끔하고 명확한 스타일을 가지고 있습니다. 이러한 최적화에는 다음이 포함됩니다.

  • 컨텍스트에서 매개 변수 및 반환 값 유형 유추
  • 단일 표현식 클로저의 암시 적 반환
  • 약식 인수 이름
  • 후행 폐쇄 구문

Closure Expressions
클로져 표현

중첩 함수[Nested Functions]에 도입 된 중첩 함수는 더 큰 함수의 일부로 자체 포함 된 코드 블록의 이름을 지정하고 정의하는 편리한 수단입니다. 그러나 전체 선언 및 이름 없이 함수와 유사한 구조의 짧은 버전을 작성하는 것이 유용 할 때도 있습니다. 함수를 하나 이상의 인수로 사용하는 함수 나 메서드로 작업 할 때 특히 그렇습니다.

클로저 표현식은 간단하고 집중적인 구문으로 인라인 클로저를 작성하는 방법입니다. 클로저 표현식은 명확성이나 의도를 잃지 않고 짧은 형태로 클로저를 작성하기위한 몇 가지 구문 최적화를 제공합니다. 아래의 클로저 표현식 예제는 여러 반복에 걸쳐 sorted(by : ) 메서드의 단일 예제를 구체화하여 이러한 최적화를 보여줍니다. 각 예제는 동일한 기능을보다 간결한 방식으로 표현합니다.

The Sorted Method
정렬 된 메서드

Swift의 표준 라이브러리는 사용자가 제공하는 정렬 클로저의 출력을 기반으로 알려진 유형의 값 배열을 정렬하는 sorted(by : )라는 메소드를 제공합니다. 정렬 프로세스가 완료되면 sorted(by : ) 메서드는 올바른 정렬 순서로 요소를 사용하여 이전 배열과 동일한 유형 및 크기의 새 배열을 반환합니다. 원래 배열은 sorted(by : ) 메서드로 수정되지 않습니다.

아래 클로저 표현식 예제는 sorted(by : ) 메서드를 사용하여 문자열 값의 배열을 알파벳 역순으로 정렬합니다. 정렬 할 초기 배열은 다음과 같습니다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

sorted(by : ) 메서드는 배열의 내용과 동일한 유형의 두 인수를 사용하는 클로저를 허용하고 값이 정렬 된 후 첫 번째 값이 두 번째 값 앞 또는 뒤에 표시되어야하는지 여부를 나타내는 Bool 값을 반환합니다. 정렬 클로저는 첫 번째 값이 두 번째 값 앞에 나타나야하는 경우 true를 반환하고 그렇지 않으면 false를 반환해야합니다.

이 예제는 문자열 값의 배열을 정렬하므로 정렬 클로저는 (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"]

첫 번째 문자열 (s1)이 두 번째 문자열 (s2)보다 크면 backward (_ : _ 🙂 함수는 true를 반환하여 정렬 된 배열에서 s1이 s2 앞에 나타나야 함을 나타냅니다. 문자열에있는 문자의 경우 “보다 큼”은 “알파벳에서 다음에 나타남”을 의미합니다. 이것은 문자 “B”가 문자 “A”보다 “큼”이고 문자열 “Tom”이 문자열 “Tim”보다 큼을 의미합니다. 이렇게하면 “Barry”가 “Alex”앞에 배치되는 등 알파벳 역순으로 정렬됩니다.

그러나 이것은 본질적으로 단일 표현식 함수 (a> b)를 작성하는 다소 긴 방식입니다. 이 예제에서는 클로저 표현식 구문을 사용하여 정렬 클로저를 인라인으로 작성하는 것이 좋습니다.

Closure Expression 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 } )

인라인 클로저 표현식으로 함수 나 메서드에 클로저를 전달할 때 항상 매개 변수 유형과 반환 유형을 유추 할 수 있습니다. 결과적으로 클로저가 함수 또는 메서드 인수로 사용될 때 인라인 클로저를 최대한 완전한 형태로 작성할 필요가 없습니다.

그럼에도 불구하고 원하는 경우 유형을 명시 적으로 만들 수 있으며 코드 독자의 모호성을 피할 수 있다면 그렇게하는 것이 좋습니다. sorted (by 🙂 메서드의 경우, 정렬이 발생한다는 사실에서 클로저의 목적이 명확하며, 독자가 클로저가 String 값으로 작동 할 가능성이 있다고 가정하는 것이 안전합니다. 문자열 배열 정렬을 지원합니다.

Implicit Returns from Single-Expression Closures
단일 표현식 클로저의 암시 적 반환

단일 표현식 클로저는 이전 예제의이 버전에서와 같이 선언에서 return 키워드를 생략하여 단일 표현식의 결과를 암시 적으로 반환 할 수 있습니다.

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

여기서 sorted (by : ) 메서드의 인수의 함수 유형은 클로저에 의해 Bool 값이 반환되어야 함을 명확하게합니다. 클로저의 본문에는 Bool 값을 반환하는 단일 표현식 (s1> s2)이 포함되어 있기 때문에 모호성이없고 return 키워드를 생략 할 수 있습니다.

Shorthand Argument Names
짧은 인수 이름

Swift는 자동으로 인라인 클로저에 짧은 인수 이름을 제공하며, 이는 $ 0, $ 1, $ 2 등의 이름으로 클로저 인수 값을 참조하는 데 사용할 수 있습니다.

클로저 표현식 내에서 이러한 약식 인수 이름을 사용하는 경우 정의에서 클로저의 인수 목록을 생략 할 수 있으며 약식 인수 이름의 수와 유형은 예상 함수 유형에서 유추됩니다. 클로저 표현식이 전체 본문으로 구성되기 때문에 in 키워드도 생략 할 수 있습니다.

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

여기서 $ 0와 $ 1은 클로저의 첫 번째와 두 번째 문자열 인수를 나타냅니다.

Operator Methods
연산자 메서드

실제로 위의 클로저 표현식을 작성하는 더 짧은 방법이 있습니다. Swift의 String 유형은보다 큼 연산자 (>)의 문자열 별 구현을 String 유형의 두 매개 변수를 갖는 메소드로 정의하고 Bool 유형의 값을 리턴합니다. 이것은 sorted (by : ) 메서드에 필요한 메서드 유형과 정확히 일치합니다. 따라서보다 큼 연산자를 간단히 전달할 수 있으며 Swift는 문자열 특정 구현을 사용하고 싶다고 추론합니다.

reversedNames = names.sorted(by: >)

Trailing Closures
후행 폐쇄

함수의 마지막 인수로 함수에 클로저 표현식을 전달해야하고 클로저 표현식이 길면 대신 후행 클로저로 작성하는 것이 유용 할 수 있습니다. 후행 클로저가 여전히 함수에 대한 인수 인 경우에도 함수 호출의 괄호 뒤에 후행 클로저를 작성합니다. 후행 클로저 구문을 사용하는 경우 함수 호출의 일부로 첫 번째 클로저에 대한 인수 레이블을 작성하지 않습니다. 함수 호출에는 여러 개의 후행 클로저가 포함될 수 있습니다. 그러나 아래의 처음 몇 가지 예에서는 단일 후행 클로저를 사용합니다.

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 함수 본문이 여기에 표시됩니다.
}
// 후행 클로저를 사용하지 않고이 함수를 호출하는 방법은 다음과 같습니다.

someFunctionThatTakesAClosure(closure: {
    // 함수 본문이 여기에 표시됩니다.
})
// 대신 후행 클로저를 사용하여이 함수를 호출하는 방법은 다음과 같습니다.

someFunctionThatTakesAClosure() {
    // 후행 폐쇄의 본문이 여기에 표시됩니다.
}

위의 Closure Expression Syntax 섹션의 문자열 정렬 클로저는 정렬 된 (by : ) 메서드의 괄호 외부에 후행 클로저로 쓸 수 있습니다.

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

클로저 표현식이 함수 또는 메서드의 유일한 인수로 제공되고 해당 표현식을 후행 클로저로 제공하는 경우 함수를 호출 할 때 함수 또는 메서드 이름 뒤에 한 쌍의 괄호 ()를 쓸 필요가 없습니다.

reversedNames = names.sorted { $0 > $1 }

후행 클로저는 클로저가 충분히 길어서 한 줄에 인라인으로 작성할 수 없을 때 가장 유용합니다. 예를 들어, Swift의 Array 유형에는 단일 인수로 클로저 표현식을 취하는 맵 (_ : ) 메소드가 있습니다. 클로저는 배열의 각 항목에 대해 한 번 호출되고 해당 항목에 대한 대체 매핑 된 값 (다른 유형일 수 있음)을 반환합니다. 매핑 (_ : )에 전달하는 클로저에 코드를 작성하여 매핑의 특성과 반환 된 값의 유형을 지정합니다.

제공된 클로저를 각 배열 요소에 적용한 후 map (_ : ) 메서드는 원래 배열의 해당 값과 동일한 순서로 모든 새 매핑 된 값을 포함하는 새 배열을 반환합니다.

다음은 후행 클로저가있는 map (_ : ) 메서드를 사용하여 Int 값 배열을 문자열 값 배열로 변환하는 방법입니다. 배열 [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]

위의 코드는 정수 숫자와 영어 버전 이름 간의 매핑 사전을 만듭니다. 또한 문자열로 변환 할 준비가 된 정수 배열을 정의합니다.

이제 숫자 배열을 사용하여 배열의 맵 (_ : ) 메서드에 후행 클로저로 클로저 표현식을 전달하여 문자열 값의 배열을 만들 수 있습니다.

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
}
// 문자열은 [String] 유형으로 추론됩니다.
// 값은 [ "OneSix", "FiveEight", "FiveOneZero"]입니다.

map (_ : ) 메서드는 배열의 각 항목에 대해 한 번씩 클로저 표현식을 호출합니다. 매핑 할 배열의 값에서 유형을 추론 할 수 있으므로 클로저의 입력 매개 변수 유형 인 번호를 지정할 필요가 없습니다.

이 예에서 변수 번호는 클로저의 숫자 매개 변수 값으로 초기화되므로 값은 클로저 본문 내에서 수정할 수 있습니다. (함수 및 클로저에 대한 매개 변수는 항상 상수입니다.) 클로저 표현식은 매핑 된 출력 배열에 저장 될 유형을 나타 내기 위해 반환 유형 인 String도 지정합니다.

클로저 표현식은 호출 될 때마다 output이라는 문자열을 만듭니다. 나머지 연산자 (number % 10)를 사용하여 숫자의 마지막 숫자를 계산하고이 숫자를 사용하여 digitNames 사전에서 적절한 문자열을 찾습니다. 클로저는 0보다 큰 정수의 문자열 표현을 만드는 데 사용할 수 있습니다.

NOTE
digitNames 사전의 아래 첨자 호출 뒤에는 느낌표 (!)가옵니다. 사전 아래 첨자는 키가 없으면 사전 조회가 실패 할 수 있음을 나타내는 옵셔널 값을 반환하기 때문입니다. 위의 예에서 number % 10은 항상 digitNames 사전에 유효한 아래 첨자 키임을 보장하므로 아래 첨자의 옵셔널 반환 값에 저장된 문자열 값을 강제로 풀기 위해 느낌표가 사용됩니다.

digitNames 사전에서 검색된 문자열이 출력 앞에 추가되어 숫자의 문자열 버전을 역순으로 효과적으로 구축합니다. (표현식 number % 10은 16의 경우 6, 58의 경우 8, 510의 경우 0의 값을 제공합니다.)

그런 다음 숫자 변수를 10으로 나눕니다. 정수이기 때문에 나누는 동안 내림되므로 16은 1이되고 58은 5가되고 510은 51이됩니다.

숫자가 0이 될 때까지 프로세스가 반복됩니다. 이때 출력 문자열은 클로저에 의해 반환되고 map (_ : ) 메서드에 의해 출력 배열에 추가됩니다.

위의 예에서 후행 클로저 구문을 사용하면 클로저가 지원하는 함수 바로 뒤에있는 클로저의 기능을 깔끔하게 캡슐화합니다. 전체 클로저를 맵 (_ : ) 메서드의 바깥 괄호 안에 감쌀 필요가 없습니다.

함수가 여러 클로저를 사용하는 경우 첫 번째 후행 클로저에 대한 인수 레이블을 생략하고 나머지 후행 클로저에 레이블을 지정합니다. 예를 들어 아래 함수는 사진 갤러리에 대한 그림을로드합니다.

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

이 함수를 호출하여 그림을로드 할 때 두 가지 클로저를 제공합니다. 첫 번째 클로저는 성공적인 다운로드 후 그림을 표시하는 완료 핸들러입니다. 두 번째 클로저는 사용자에게 오류를 표시하는 오류 처리기입니다.

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print ( "다음 사진을 다운로드 할 수 없습니다.")
}

이 예제에서 loadPicture (from : complete : onFailure : ) 함수는 네트워크 작업을 백그라운드로 전달하고 네트워크 작업이 완료되면 두 완료 핸들러 중 하나를 호출합니다. 이러한 방식으로 함수를 작성하면 두 상황을 모두 처리하는 하나의 클로저를 사용하는 대신 다운로드 성공 후 사용자 인터페이스를 업데이트하는 코드에서 네트워크 오류를 처리하는 코드를 명확하게 구분할 수 있습니다.

Capturing Values
캡쳐 값

클로저는 정의 된 주변 컨텍스트에서 상수와 변수를 캡처 할 수 있습니다. 그런 다음 클로저는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않더라도 본문 내에서 해당 상수 및 변수의 값을 참조하고 수정할 수 있습니다.

Swift에서 값을 캡처 할 수 있는 가장 간단한 형태의 클로저는 다른 함수의 본문 내에 작성된 중첩 함수입니다. 중첩 함수는 외부 함수의 인수를 캡처 할 수 있으며 외부 함수 내에 정의 된 모든 상수 및 변수를 캡처 할 수도 있습니다.

다음은 incrementer라는 중첩 함수를 포함하는 makeIncrementer라는 함수의 예입니다. 중첩 incrementer() 함수는 주변 컨텍스트에서 runningTotal 및 amount라는 두 값을 캡처합니다. 이러한 값을 캡처 한 후에는 호출 될 때마다 runningTotal을 양만큼 증가시키는 클로저로 incrementer가 makeIncrementer에 의해 반환됩니다.

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

makeIncrementer의 반환 유형은 ()-> Int입니다. 이것은 단순한 값이 아닌 함수를 반환 함을 의미합니다. 반환하는 함수에는 매개 변수가 없으며 호출 될 때마다 Int 값을 반환합니다. 함수가 다른 함수를 반환하는 방법을 알아 보려면 반환 유형으로서의 함수 유형 단원[Function Types as Return Types.]을 참조하세요.

makeIncrementer(forIncrement : ) 함수는 반환 될 증가분의 현재 누계를 저장하기 위해 runningTotal이라는 정수 변수를 정의합니다. 이 변수는 0 값으로 초기화됩니다.

makeIncrementer(forIncrement 🙂 함수에는 인수 레이블이 forIncrement이고 매개 변수 이름이 amount 인 단일 Int 매개 변수가 있습니다. 이 매개 변수에 전달 된 인수 값은 반환 된 증분 함수가 호출 될 때마다 runningTotal을 얼마나 늘려야하는지 지정합니다. makeIncrementer 함수는 실제 증가를 수행하는 incrementer라는 중첩 함수를 정의합니다. 이 함수는 단순히 runningTotal에 금액을 더하고 결과를 반환합니다.

개별적으로 고려할 때 중첩 된 incrementer() 함수는 비정상적으로 보일 수 있습니다.

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

incrementer() 함수에는 매개 변수가 없지만 해당 함수 본문 내에서 runningTotal 및 amount를 참조합니다. 주변 함수에서 runningTotal 및 amount에 대한 참조를 캡처하고 자체 함수 본문 내에서 사용하여 이를 수행합니다. 참조로 캡처하면 makeIncrementer 호출이 종료 될 때 runningTotal 및 amount가 사라지지 않고 다음에 incrementer 함수가 호출 될 때 runningTotal을 사용할 수 있습니다.

NOTE
최적화로 Swift는 값이 클로저에 의해 변경되지 않고 클로저가 생성 된 후 값이 변경되지 않은 경우 값의 사본을 캡처하고 저장할 수 있습니다.
또한 Swift는 더 이상 필요하지 않을 때 변수 처리와 관련된 모든 메모리 관리를 처리합니다.

다음은 작동중인 makeIncrement의 예입니다.

let incrementByTen = makeIncrementer(forIncrement: 10)

이 예제에서는 호출 될 때마다 runningTotal 변수에 10을 더하는 증분 함수를 참조하도록 incrementByTen이라는 상수를 설정합니다. 함수를 여러 번 호출하면이 동작이 실제로 표시됩니다.

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

두 번째 증분자를 만들면 별도의 새 runningTotal 변수에 대한 자체 저장된 참조가 있습니다.

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

원래 incrementer 를 다시 호출하면 자체 runningTotal 변수가 계속 증가하고 incrementBySeven에 의해 캡처 된 변수에는 영향을 주지 않습니다.

incrementByTen()
// returns a value of 40

NOTE
클래스 인스턴스의 속성에 클로저를 할당하고 클로저가 인스턴스 또는 해당 멤버를 참조하여 해당 인스턴스를 캡처하면 클로저와 인스턴스 사이에 강한 순환참조가 생성됩니다. Swift는 캡처 목록을 사용하여 이러한 강력한 참조주기를 깨뜨립니다. 자세한 내용은 폐쇄에 대한 강력한 참조주기[Strong Reference Cycles for Closures.]를 참조하세요.

Closures Are Reference Types
클로저는 참조 타입

위의 예에서 incrementBySeven 및 incrementByTen은 상수이지만 이러한 상수가 참조하는 클로저는 캡처 한 runningTotal 변수를 계속 증가시킬 수 있습니다. 이는 함수와 클로저가 참조 유형이기 때문입니다.

함수 또는 클로저를 상수 또는 변수에 할당 할 때마다 실제로 해당 상수 또는 변수를 함수 또는 클로저에 대한 참조로 설정합니다. 위의 예에서 incrementByTen은 클로저 자체의 내용이 아니라 상수를 참조하는 클로저 선택입니다.

이것은 또한 두 개의 다른 상수 또는 변수에 클로저를 할당하면 해당 상수 또는 변수 모두 동일한 클로저를 참조한다는 것을 의미합니다.

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

incrementByTen()
// returns a value of 60

위의 예는 alsoIncrementByTen을 호출하는 것이 incrementByTen을 호출하는 것과 동일 함을 보여줍니다. 둘 다 동일한 클로저를 참조하기 때문에 둘 다 동일한 누적 합계를 증가시키고 반환합니다.

Escaping Closures
이스케이핑 클로저

클로저는 클로저가 함수에 인수로 전달 될 때 함수를 이스케이프한다고 말하지만 함수가 반환 된 후에 호출됩니다. 매개 변수 중 하나로 클로저를받는 함수를 선언 할 때 매개 변수 유형 앞에 @escaping을 작성하여 클로저가 이스케이프 될 수 있음을 나타낼 수 있습니다.

클로저가 탈출 할 수있는 한 가지 방법은 함수 외부에 정의 된 변수에 저장하는 것입니다. 예를 들어, 비동기 작업을 시작하는 많은 함수는 완료 핸들러로 클로저 인수를 사용합니다. 이 함수는 작업을 시작한 후 반환되지만 작업이 완료 될 때까지 클로저가 호출되지 않습니다. 클로저는 나중에 호출하려면 이스케이프해야합니다. 예를 들면 :

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

someFunctionWithEscapingClosure (_ : ) 함수는 클로저를 인수로 사용하여 함수 외부에서 선언 된 배열에 추가합니다. 이 함수의 매개 변수를 @escaping으로 표시하지 않으면 컴파일 타임 오류가 발생합니다.

self를 참조하는 이스케이프 클로저는 self가 클래스의 인스턴스를 참조하는 경우 특별한 고려가 필요합니다. 이스케이프 클로저에서 자신을 캡처하면 실수로 강한 순환참조를 쉽게 만들 수 있습니다. 참조주기에 대한 자세한 내용은 [Automatic Reference Counting.]을 참조하세요.

일반적으로 클로저는 클로저 본문에서 변수를 사용하여 암시적으로 변수를 캡처하지만 이 경우 명시 적이어야합니다. self를 캡처하려면 self를 사용 할 때 명시 적으로 작성하거나 클로저의 캡처 목록에 self를 포함합니다. 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"

다음은 클로저의 캡처 목록에 포함하여 자신을 캡처 한 다음 암시 적으로 self를 참조하는 doSomething() 버전입니다.

self가 구조체의 인스턴스이거나 열거형이면 항상 암시 적으로 self를 참조 할 수 있습니다. 그러나 이스케이프 클로저는 self가 구조체 또는 열거 형의 인스턴스 일 때 self에 대한 변경 가능한 참조를 캡처 할 수 없습니다. 구조 및 열거는 값 유형에 설명 된대로 구조체 및 열거형은 공유 변경을 허용하지 않습니다.

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

위의 예에서 someFunctionWithEscapingClosure 함수에 대한 호출은 변경 메서드 내부에 있기 때문에 오류이므로 self는 변경 가능합니다. 이는 클로처를 이스케이프하면 구조에 대한 self에 대한 변경 가능한 참조를 캡처 할 수 없다는 규칙을 위반합니다.

Autoclosures
자동 클로저

자동 클로저는 함수에 인수로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저입니다. 인수를 사용하지 않으며 호출 될 때 내부에 래핑 된 표현식의 값을 반환합니다. 이러한 구문상의 편리함을 통해 명시 적 클로저 대신 일반 표현식을 작성하여 함수 매개 변수 주위의 중괄호를 생략 할 수 있습니다.

자동 클로저를 취하는 함수를 호출하는 것은 일반적이지만 그런 종류의 함수를 구현하는 것은 일반적이지 않습니다. 예를 들어 assert (condition : message : file : line : ) 함수는 조건 및 메시지 매개 변수에 대해 자동 클로저를 사용합니다. 조건 매개 변수는 디버그 빌드에서만 평가되고 해당 메시지 매개 변수는 조건이 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 "이제 크리스에게 봉사!"
print(customersInLine.count)
// Prints "4"

customersInLine 배열의 첫 번째 요소는 클로저 내부의 코드에 의해 제거되지만 실제로 클로저가 호출 될 때까지 배열 요소는 제거되지 않습니다. 클로저가 호출되지 않으면 클로저 내부의 표현식이 평가되지 않으므로 배열 요소가 제거되지 않습니다. customerProvider의 유형은 문자열이 아니라 () -> String—a 문자열을 반환하는 매개 변수가없는 함수입니다.

함수에 대한 인수로 클로저를 전달할 때 지연된 평가의 동일한 동작을 얻습니다.

// 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 인수의 값은 함수의 범위를 벗어날 수 있어야합니다.

에 발행했습니다
SWIFT(으)로 분류되었습니다

aaron님이 작성

아무것도 안해도 시간은 흐른다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다