Closures

Groot·2022년 6월 27일
0

Swift Language Guide

목록 보기
10/24
post-thumbnail
post-custom-banner

Closures

  • Closure는 전달되고 코드에서 사용할 수 있는 자급식 기능 블록입니다.
  • Swift의 Closure는 C 및 Objective-C의 블록 및 다른 프로그래밍 언어의 람다와 유사합니다.
  • Closure는 정의된 Context에서 모든 상수 및 변수에 대한 참조를 캡처하고 저장할 수 있습니다.
  • 이것은 해당 상수와 변수를 닫는 것으로 알려져 있습니다 . Swift는 캡처의 모든 메모리 관리를 처리합니다.
  • Functions 에서 소개된 전역 및 중첩 함수 는 실제로 Closure의 특별한 경우입니다. Closure는 다음 세 가지 형식 중 하나를 취합니다.
    • 전역 함수는 이름이 있고 값을 캡처하지 않는 Closure입니다.
    • 중첩된 함수는 이름이 있고 둘러싸는 함수에서 값을 캡처할 수 있는 Closure입니다.
    • Closure 표현식은 주변 Context에서 값을 캡처할 수 있는 간단한 구문으로 작성된 명명되지 않은 Closure입니다.
  • Swift의 Closure 표현식은 일반 시나리오에서 간결하고 깔끔한 구문을 권장하는 최적화와 함께 깨끗하고 명확한 스타일을 가지고 있습니다. 이러한 최적화에는 다음이 포함됩니다.
    • Inferring parameter and return value types from context
    • Implicit returns from single-expression closures
    • Shorthand argument names
    • Trailing closure syntax

📌 Closure Expressions

  • Nested Functions 에 소개된 중첩 함수 는 더 큰 함수의 일부로 자체 포함된 코드 블록의 이름을 지정하고 정의하는 편리한 수단입니다.
  • 그러나 때로는 완전한 선언과 이름 없이 함수와 유사한 구조의 더 짧은 버전을 작성하는 것이 유용합니다.
  • 하나 이상의 매개변수로 함수를 사용하는 함수나 메서드로 작업할 때 특히 그렇습니다.
  • Closure 표현식 은 간결하고 집중된 구문으로 인라인 Closure를 작성하는 방법입니다.
  • Closure 식은 명확성이나 의도의 손실 없이 짧은 형식으로 Closure를 작성하기 위한 여러 구문 최적화를 제공합니다.
  • 아래의 Closure 표현식 예제는 sorted(by:)여러 반복에 걸쳐 메서드의 단일 예제를 개선하여 이러한 최적화를 보여줍니다.
  • 각 예제는 동일한 기능을 보다 간결하게 표현합니다.

📍 The Sorted Method

  • Swift의 표준 라이브러리는 제공한 정렬 Closure의 출력을 기반으로 배열을 정렬하는 sorted(by:)라는 메서드를 제공합니다.

  • 정렬 프로세스가 완료되면 이 sorted(by:)메서드는 이전 배열과 동일한 유형 및 크기의 새 배열을 반환하며 요소가 올바른 정렬 순서로 포함되어 있습니다.

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

    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
  • 이 sorted(by:)메서드는 배열의 내용과 동일한 유형의 두 매개변수를 사용하는 Closure를 허용하고 Bool값이 정렬되면 첫 번째 값이 두 번째 값 앞이나 뒤에 나타나야 하는지 여부를 나타내는 값을 반환합니다.

  • 정렬 Closure는 첫 번째 값이 두 번째 값보다 먼저 나타나야 하는 경우 true를 반환해야 하고, 그렇지 않은 경우 반환해야 합니다.false

  • 이 예제는 String값 배열을 정렬하는 것이므로 정렬 Closure는 (String, String) -> Bool type의 함수여야 합니다 .

  • sorted(by:)정렬 Closure를 제공하는 한 가지 방법은 올바른 유형의 일반 함수를 작성하고 메서드 에 대한 매개변수로 전달하는 것입니다 .

    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)보다 크면 reverse(::) 함수는 true를 반환하여 정렬된 배열에서 s1이 s2보다 먼저 나타나야 함을 나타냅니다.

  • 문자열에 있는 문자의 경우 "greater than"은 "appears later in the alphabet than"을 의미합니다. 이것은 문자 "B"가 문자 "A"보다 "크고" 문자열 "Tom"이 문자열 "Tim"보다 크다는 것을 의미합니다. 이렇게 하면 "Alex" 앞에 "Barry"가 배치되는 등 알파벳 역순으로 정렬됩니다

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

📍 Closure Expression Syntax

  • Closure 표현식 구문은 다음과 같은 일반 형식을 가집니다.

    { (parameters) -> return type in
    statements
    }

  • Closure 표현식 구문 의 매개변수 는 인-아웃 매개변수가 될 수 있지만 기본값을 가질 수는 없습니다. 가변 매개변수의 이름을 지정하면 가변 매개변수를 사용할 수 있습니다. 튜플은 매개변수 유형 및 반환 유형으로도 사용할 수 있습니다.
  • 아래 예제는 backward(::)위 함수의 Closure 표현식 버전을 보여줍니다.
    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })
  • 이 인라인 Closure에 대한 매개변수 및 반환 유형의 선언은 backward(::) 함수의 선언과 동일합니다. 두 경우 모두 (s1: String, s2: String) -> Bool로 작성됩니다.
  • 그러나 인라인 Closure 표현식의 경우 매개변수와 반환 유형은 중괄호 외부가 아니라 중괄호 내부에 작성됩니다.
  • Closure 본문의 시작은 in키워드로 시작됩니다. 이 키워드는 Closure의 매개변수 및 반환 유형 정의가 완료되었고 Closure 본문이 시작될 것임을 나타냅니다.
  • Closure의 본문은 너무 짧기 때문에 한 줄로 작성할 수도 있습니다.
    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
  • 이는 sorted(by:)메서드에 대한 전체 호출이 동일하게 유지되었음을 보여줍니다. 한 쌍의 괄호는 여전히 메서드에 대한 전체 매개변수를 래핑합니다. 그러나 그 매개변수는 이제 인라인 Closure입니다.

📍 Inferring Type From Context

  • 정렬 Closure는 메서드에 대한 매개변수로 전달되기 때문에 Swift는 매개변수의 유형과 반환하는 값의 유형을 유추할 수 있습니다. 메서드 는 sorted(by:)문자열 배열에서 호출되므로 해당 매개변수는 유형의 함수여야 합니다 .
  • 이는 및 유형이 Closure 표현식 정의의 일부로 작성될 필요가 없음을 의미합니다. 모든 유형을 유추할 수 있으므로 반환 화살표( )와 매개변수 이름 주위의 괄호도 생략할 수 있습니다. (String, String) -> Bool(String, String)Bool->
    reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
  • 인라인 Closure 표현식으로 함수나 메서드에 Closure를 전달할 때 매개변수 유형과 반환 유형을 항상 유추할 수 있습니다.
  • 결과적으로 Closure가 함수 또는 메서드 매개변수로 사용될 때 인라인 Closure를 완전한 형태로 작성할 필요가 없습니다.
  • 그럼에도 불구하고 원하는 경우 유형을 명시적으로 만들 수 있으며 코드 독자의 모호성을 방지하는 경우 그렇게 하는 것이 좋습니다.

📍 Implicit Returns from Single-Expression Closures

  • return단일 표현식 Closure 는 이전 예제의 이 버전에서와 같이 선언에서 키워드를 생략하여 단일 표현식의 결과를 암시적으로 반환할 수 있습니다 .
    reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
  • 여기에서 메서드 매개변수의 함수 유형은 값이 Closure에서 반환되어야 함 sorted(by:)을 분명히 합니다 . Closure의 본문에는 값 을 반환하는 Bool단일 표현식( )이 포함되어 있으므로 모호성이 없고 키워드를 생략할 수 있습니다.s1 > s2Boolreturn

📍 Shorthand Argument Names

  • Swift는 인라인 Closure에 자동으로 약식 매개변수 이름을 제공합니다. 이는 $0, $1, $2 등의 이름으로 Closure의 매개변수 값을 참조하는 데 사용할 수 있습니다.
  • Closure 표현식 내에서 이러한 축약형 매개변수 이름을 사용하는 경우 정의에서 Closure의 매개변수 목록을 생략할 수 있습니다.
  • 약식 매개변수 이름의 유형은 예상되는 함수 유형에서 유추되며 사용하는 가장 높은 번호의 단축 매개변수는 Closure가 취하는 매개변수의 수를 결정합니다.
  • Closure 표현식 이 in전체 본문으로 구성되어 있기 때문에 키워드를 생략할 수도 있습니다.
    reversedNames = names.sorted(by: { $0 > $1 } )
  • 여기서 $0 및 $1은 Closure의 첫 번째 및 두 번째 String 매개변수를 나타냅니다.
  • $1이 가장 높은 숫자의 축약형 매개변수이기 때문에 Closure는 두 개의 매개변수를 취하는 것으로 이해됩니다.
  • 여기에서 sorted(by:) 함수는 매개변수가 모두 문자열인 Closure를 예상하기 때문에 약식 매개변수 $0 및 $1은 모두 문자열 유형입니다.

📍 Operator Methods

  • 실제로 위의 Closure 표현식을 작성하는 더 짧은 방법이 있습니다.
  • Swift의 String 유형은 String 유형의 매개변수가 두 개 있고 Bool 유형의 값을 리턴하는 메소드로 greater-than operator(>)의 문자열 구현을 정의합니다.
  • 이것은 sorted(by:) 메소드에 필요한 메소드 유형과 정확히 일치합니다.
  • 따라서 단순히 greater-than operator(>)를 전달할 수 있으며, Swift는 이것을 문자열 특정 구현을 사용하기를 원한다고 추론합니다.
    reversedNames = names.sorted(by: >)

📌 Trailing Closures

  • Closure 표현식을 함수의 최종 매개변수로 함수에 전달해야 하고 Closure 표현식이 길다면, Trailing Closure로 작성하는 것이 유용할 수 있습니다.

  • Trailing Closure가 여전히 함수에 대한 매개변수임에도 불구하고 함수 호출의 괄호 뒤에 Trailing Closure를 작성합니다.

  • Trailing Closure 구문을 사용할 때 함수 호출의 일부로 첫 번째 Closure에 대한 매개변수 레이블을 작성하지 않습니다.

  • 함수 호출에는 여러 개의 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
    }
  • 위의 Closure 표현식 구문 섹션 의 문자열 정렬 Closure 는 sorted(by:)메서드의 괄호 외부에 Trailing Closure로 작성할 수 있습니다.

    reversedNames = names.sorted() { $0 > $1 }
  • 클로저 표현식이 함수 또는 메소드의 유일한 인수로 제공되고 해당 표현식을 후행 클로저로 제공하면, 함수를 호출할 때 함수 또는 메소드 이름 뒤에 한 쌍의 괄호()를 쓸 필요가 없습니다.

    reversedNames = names.sorted { $0 > $1 }
  • Trailing Closure는 Closure가 충분히 길어서 한 줄에 인라인으로 작성할 수 없을 때 가장 유용합니다.

  • 예를 들어, Swift의 Array유형에는 단일 매개변수로 Closure 표현식을 사용하는 map(_:)메소드가 있습니다.

  • Closure는 배열의 각 항목에 대해 한 번 호출되고 해당 항목에 대해 대체 매핑된 값(다른 유형일 수 있음)을 반환합니다.

  • map(_:).에 전달하는 Closure에 코드를 작성하여 매핑의 특성과 반환된 값의 유형을 지정합니다

  • 제공된 Closure를 각 배열 요소에 적용한 후 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(_:) 메서드에 클로저 표현식을 후행 클로저로 전달하여 문자열 값 배열을 만들 수 있습니다.

    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(_:)메서드는 배열의 각 항목에 대해 Closure 식을 한 번 호출합니다.

  • number매핑할 배열의 값에서 유형을 유추할 수 있으므로 Closure의 입력 매개변수 유형을 지정할 필요가 없습니다.

  • 이 예제에서 변수 number는 클로저의 number 매개변수 값으로 초기화되므로 클로저 본문 내에서 값을 수정할 수 있습니다. (함수와 클로저에 대한 매개변수는 항상 상수입니다.)

  • Closure 표현식 String은 매핑된 출력 배열에 저장될 유형을 나타내기 위해 String의 반환 유형을 지정합니다.

  • Closure 표현식은 호출될 때마다 output이라는 문자열을 생성합니다.

    digitNames dictionary의 subscript에 대한 호출 뒤에 느낌표(!)가 옵니다. dictionary’s subscript 는 키가 존재하지 않는 경우 dictionary 조회가 실패할 수 있음을 나타내는 optional value을 반환하기 때문입니다.

    위의 예에서 number % 10은 항상 digitNames dictionary의 유효한 key임을 보장하므로 subscript의 optional value에 저장된 문자열 값을 강제로 풀기 위해 느낌표가 사용됩니다.

  • 위의 예에서 후행 클로저 구문을 사용하면 map(_:) 메서드의 외부 괄호 안에 전체 클로저를 래핑할 필요 없이 클로저가 지원하는 함수 바로 뒤에 클로저의 기능을 깔끔하게 캡슐화합니다.

  • 함수가 여러 Closure를 사용하는 경우 첫 번째 Trailing Closure에 대한 매개변수 레이블을 생략하고 나머지 Trailing Closure에 레이블을 지정합니다.

    func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
        if let picture = download("photo.jpg", from: server) {
            completion(picture)
        } else {
            onFailure()
        }
    }
  • 이 함수를 호출하여 그림을 로드할 때 두 개의 Closure를 제공합니다.

  • 첫 번째 Closure는 성공적인 다운로드 후 그림을 표시하는 완료 핸들러입니다.

  • 두 번째 Closure는 사용자에게 오류를 표시하는 오류 처리기입니다.

    loadPicture(from: someServer) { picture in
        someView.currentPicture = picture
    } onFailure: {
        print("Couldn't download the next picture.")
    }
  • 이 예제에서 loadPicture(from:completion:onFailure:)함수는 네트워크 작업을 백그라운드로 디스패치하고 네트워크 작업이 완료되면 두 개의 완료 핸들러 중 하나를 호출합니다.

  • 이 방법으로 함수를 작성하면 두 상황을 모두 처리하는 하나의 Closure를 사용하는 대신 성공적인 다운로드 후 사용자 인터페이스를 업데이트하는 코드에서 네트워크 오류 처리를 담당하는 코드를 명확하게 분리할 수 있습니다.

    특히 여러 핸들러를 중첩해야 하는 경우 완료 핸들러를 읽기 어려울 수 있습니다. 다른 접근 방식은 동시성에 설명된 대로 비동기 코드를 사용하는 것입니다.


📌 Capturing Values

  • Closure는 정의된 주변 Context에서 상수와 변수를 캡처 할 수 있습니다.
  • 그러면 Closure는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않는 경우에도 본문 내에서 해당 상수 및 변수의 값을 참조하고 수정할 수 있습니다.
  • Swift에서 값을 캡처할 수 있는 가장 간단한 형태의 Closure는 다른 함수의 본문 내에 작성된 중첩 함수입니다.
  • 중첩 함수는 외부 함수의 모든 매개변수를 캡처할 수 있으며 외부 함수 내에 정의된 모든 상수 및 변수도 캡처할 수 있습니다.
  • 다음은 incrementer라는 중첩 함수를 포함하는 makeIncrementer라는 함수의 예입니다.
  • 중첩된 incrementer() 함수는 주변 컨텍스트에서 runningTotal 및 amount의 두 값을 캡처합니다.
  • 이 값을 캡처한 후 incrementer는 호출될 때마다 runningTotal을 양만큼 증가시키는 클로저로 makeIncrementer에 의해 반환됩니다.
    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementer() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }
  • makeIncrementer의 반환 유형은 () -> Int입니다. 즉, 단순한 값이 아닌 함수를 반환합니다.
  • 반환하는 함수에는 매개변수가 없으며 호출될 때마다 값을 반환합니다.
  • makeIncrementer(forIncrement:) 함수는 반환될 incrementer의 현재 누계를 저장하기 위해 runningTotal이라는 정수 변수를 정의합니다.
  • makeIncrementer(forIncrement:) 함수에는 인수 레이블이 forIncrement이고 매개변수 이름이 amount인 단일 Int 매개변수가 있습니다.
  • 이 매개변수에 전달된 인수 값은 반환된 증분 함수가 호출될 때마다 runningTotal이 얼마나 증가해야 하는지를 지정합니다.
  • makeIncrementer 함수는 실제 증가를 수행하는 incrementer라는 중첩 함수를 정의합니다.
  • 이 함수는 단순히 runningTotal에 금액을 추가하고 결과를 반환합니다.
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
  • incrementer() 함수에는 매개변수가 없지만 함수 본문 내에서 runningTotal 및 amount를 참조합니다.
  • 주변 함수에서 runningTotal 및 amount에 대한 참조를 캡처하고 자체 함수 본문 내에서 사용하여 이를 수행합니다.
  • 참조로 캡처하면 makeIncrementer 호출이 종료될 때 runningTotal 및 amount가 사라지지 않고 다음에 증분기 함수가 호출될 때 runningTotal을 사용할 수 있습니다.

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

  • 다음은 makeIncrementer실행의 예입니다.
    let incrementByTen = makeIncrementer(forIncrement: 10)
  • 이 예제는 호출될 때마다 runningTotal 변수에 10을 추가하는 incrementer 함수를 참조하도록 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(incrementByTen)를 다시 호출하면 runningTotal 변수가 계속 증가하고 incrementBySeven에 의해 캡처된 변수에는 영향을 미치지 않습니다.
    incrementByTen()
    // returns a value of 40

    클래스 인스턴스의 속성에 클로저를 할당하고 클로저가 인스턴스 또는 해당 멤버를 참조하여 해당 인스턴스를 캡처하는 경우 클로저와 인스턴스 사이에 강력한 참조 주기를 생성합니다.


📌 Closures Are Reference Types

  • 위의 예에서 incrementBySeven및 incrementByTen는 상수이지만 이러한 상수가 참조하는 'runningTotal' Closure는 캡처한 변수를 계속 증가시킬 수 있습니다.

  • 이는 함수와 Closure가 참조 유형 이기 때문 입니다.

  • 상수나 변수에 함수나 Closure를 할당할 때마다 실제로 해당 상수나 변수를 함수나 Closure에 대한 참조 로 설정하는 것입니다.

  • 위의 예에서 incrementByTen이 참조하는 것은 Closure의 선택이며 Closure 자체의 내용이 아니라 상수입니다.

  • 이것은 또한 두 개의 서로 다른 상수 또는 변수에 Closure를 할당하는 경우 해당 상수 또는 변수 모두가 동일한 Closure를 참조한다는 것을 의미합니다.

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // returns a value of 50
    
    incrementByTen()
    // returns a value of 60
  • 위의 예는 alsoIncrementByTen을 호출하는 것이 incrementByTen을 호출하는 것과 동일함을 보여줍니다.

  • 둘 다 동일한 Closure를 참조하기 때문에 둘 다 동일한 누계를 증가시키고 반환합니다


📌 Escaping Closures

  • Closure는 함수에 매개변수로 전달될 때 함수를 escape 한다고 말하지만 함수가 반환된 후에 호출됩니다.

  • Closure를 매개변수 중 하나로 사용하는 함수를 선언할 때 @escaping를 매개변수 유형 앞에 작성하여 Closure가 이스케이프할 수 있음을 나타낼 수 있습니다.

  • Closure가 탈출할 수 있는 한 가지 방법은 함수 외부에 정의된 변수에 저장하는 것입니다.

  • 예를 들어 비동기 작업을 시작하는 많은 함수는 Closure 매개변수를 완료 처리기로 사용합니다.

  • 함수는 작업을 시작한 후 반환하지만 작업이 완료될 때까지 Closure는 호출되지 않습니다.

  • Closure는 나중에 호출되기 위해 이스케이프해야 합니다.

    var completionHandlers: [() -> Void] = []
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler)
    }
  • someFunctionWithEscapingClosure(_:) 함수는 클로저를 인수로 취하여 함수 외부에 선언된 배열에 추가합니다.

  • 이 함수의 매개변수를 @escaping으로 표시하지 않으면 컴파일 시간 오류가 발생합니다.

  • self를 참조하는 이스케이프 클로저는 self가 클래스의 인스턴스를 참조하는 경우 특별한 고려가 필요합니다.

  • escape 클로저에서 self를 캡처하면 실수로 강력한 참조 주기를 쉽게 생성할 수 있습니다.

  • 일반적으로 Closure는 Closure 본문에서 변수를 사용하여 암시적으로 변수를 캡처하지만 이 경우에는 명시적이어야 합니다.

  • 캡처하고 싶다면 사용할 때 명시적으로 self를 작성 하거나 Closure의 캡처 목록에 self를 포함하십시오.

  • self를 명시적으로 작성하면 의도를 표현할 수 있으며 참조 주기가 없음을 확인하도록 상기시킵니다.

  • 예를 들어 아래 코드에서 someFunctionWithEscapingClosure(_:) 에 전달된 Closure는 명시적으로 self를 참조합니다 .

  • someFunctionWithNonescapingClosure(_:) 에 전달된 클로저는 nonescaping closure입니다. 즉, 암시적으로 자체를 참조할 수 있습니다.

    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를 캡처한 다음 암시적으로 self를 참조하는 doSomething() 함수입니다.

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
  • self가 구조체 또는 열거형의 인스턴스인 경우 항상 암시적으로 self를 참조할 수 있습니다.
  • 그러나 이스케이프 클로저는 self가 구조체나 열거형의 인스턴스인 경우 self에 대한 변경 가능한 참조를 캡처할 수 없습니다.
  • 구조 및 열거형은 값 유형에서 설명한 것처럼 구조 및 열거형은 공유 변경성을 허용하지 않습니다.
struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}
  • 위 예제에서 someFunctionWithEscapingClosure 함수 호출은 mutating 메소드 내부에 있기 때문에 오류이므로 self가 변경 가능합니다.
  • 이는 이스케이프 클로저가 구조체의 변경 가능한 self 참조를 캡처할 수 없다는 규칙을 위반합니다.

📌 Autoclosures

  • 자동 Closure 는 함수에 매개변수로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 Closure입니다.

  • 매개변수를 사용하지 않으며 호출될 때 내부에 래핑된 표현식의 값을 반환합니다.

  • 이러한 구문상의 편리함을 통해 명시적 Closure 대신 일반 표현식을 작성하여 함수 매개변수 주위에 중괄호{}를 생략할 수 있습니다.

  • 자동 Closure를 사용하는 함수를 호출 하는 것이 일반적이지만 그런 종류의 함수 를 구현 하는 것은 일반적이지 않습니다.

  • 예를 들어, assert(condition:message:file:line:)함수는 condition및 message매개변수에 대해 자동 Closure를 사용합니다.

  • condition 매개변수는 디버그 빌드에서만 평가되고 해당 message 매개변수는 조건이 false인 경우에만 평가됩니다 .

  • 자동 Closure를 사용하면 Closure를 호출할 때까지 내부 코드가 실행되지 않기 때문에 평가를 지연할 수 있습니다.

  • 평가 지연은 부작용이 있거나 계산 비용이 많이 드는 코드에 유용합니다.

  • 그 이유는 해당 코드가 평가되는 시기를 제어할 수 있기 때문입니다.

  • 아래 코드는 Closure가 평가를 지연시키는 방법을 보여줍니다.

    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"
  • CustomersInLine 배열의 첫 번째 요소가 클로저 내부의 코드에 의해 제거되더라도 클로저가 실제로 호출될 때까지 배열 요소는 제거되지 않습니다.

  • Closure가 호출되지 않으면 Closure 내부의 식은 평가되지 않습니다.

  • 즉, 배열 요소가 제거되지 않습니다.

  • customerProvider의 유형은 문자열이 아니라 () -> 문자열(문자열을 반환하는 매개변수가 없는 함수)입니다.

  • Closure를 함수에 대한 매개변수로 전달할 때 지연된 평가와 동일한 동작을 얻습니다.

    // 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 인수의 값은 함수의 범위를 벗어나도록 허용되어야 합니다.

profile
I Am Groot
post-custom-banner

0개의 댓글