Closures

Swift/Language Guide 2018. 8. 30. 22:15
반응형

[최종 수정일 : 2018.08.22]

원문 : 애플 개발 문서 Swift 4.2 Language Guide - Closures

클로져(Closures)

클로져(Closures)는 코드에서 전달되고 사용할수 있는 함수의 독립적인(self-contained) 블록입니다. Swift에서 클로져는 C와 Objective-C에서의 블록(blocks)과 다른 언어의 람다(lambdas)와 비슷합니다.

클로져는 컨텐스트에 정의된 모든 상수와 변수를 캡쳐하고 참조를 저장할수 있습니다. 이것은 이러한 상수와 변수를 클로징(closing over) 하는 것으로 알려져 있습니다. Swift는 캡쳐하는 모든 메모리 관리를 처리합니다.

주의
캡쳐링(catpturing)의 개념에 익숙하지 않아도 걱정하지 마세요. 그것은 아래의 Capturing Values에서 자세히 설명될 것입니다.

Functions에서 소개된, 전역과 중첩된 함수들은 실제로는 클로져의 특별한 경우(case)입니다. 클로져는 다음 3가지 중의 하나입니다.

  • 전역 함수(Global functions)는 이름을 가지는 클로져이고 어떤 값도 캡쳐하지 않습니다.
  • 중첩 함수(Nested functions)는 이름을 가지는 클로져이고 감싸고 있는 함수의 값을 캡쳐할 수 있습니다.
  • 클로져 표현은 컨텍스트(context)의 주변(surrounding) 값들을 캡쳐 할 수 있는 간단한 구문으로 작성된 이름없는 클로져 입니다.

Swift의 클로져 표현은 일반적인 시나리오 상황에서 간결(brief)하며, 혼란스럽지 않도록(clutter-free) 최적화해주는, 깨끗하고 명확한 스타일입니다. 이러한 최적화는 다음과 같습니다.

  • context에서 매개변수와 반환 값 타입을 추론하기
  • 단일 표현(single-expression) 클로져로부터 암시적으로 반환하기
  • 짧은 인자 이름
  • 후행(Trailing) 클로져 문법

클로져 표현식(Closure Expressions)

중첩 함수(Nested Functions)에서 소개된 중첩 함수들은, 큰 함수의 일부를 독립적인 코드 블록으로 이름 짓고 정의하는 편리한 방법입니다. 하지만, 가끔씩 함수와 유사한 구조의 축약 버젼으로 전체 선언과 이름 없이 작성하는게 유용할때가 있습니다. 하나 이상의 인자를 가져오는 함수를 가져오는 함수나 메소드로 작업할때 특히 그렇습니다.

클로져 표현식(Closure expressions)은 문법에 집중해서, 간결하게 인라인(Inline) 클로져를 만드는 방법입니다. 클로져 표현식은 명확성과 의도를 잃지않고 짧은 형식으로 클로져를 작성하는 몇가지 최적화 문법을 제공합니다. 아래 클로져 표현식 예제는 여러번 반복하여 sorted(by:) 메소드의 예제를 개선하여 최적화하는 것을 보여주며, 각 표현식은 동일한 기능을 더 간결하게 표현하는 방법입니다.

정렬된 메소드(The Sorted Method)

Swift의 표준 라이브러리는 여러분이 제공하는 정렬하는 클로져의 결과를 기반으로해서, 알려진 타입의 값 배열을 정렬하기 위한 sorted(by:) 메소드를 제공합니다. 한번 정렬 프로세스가 완료되면, sorted(by:) 메소드는 예전 타입과 크기가 같은 새로운 배열을 반환하며, 그 요소들은 올바른 순서로 정렬되어 있습니다.

아래 클로져 표현식 예제는 String 값의 배열을 알파벳 역순으로 정렬하기 위해, sotred(by:) 메소드를 사용합니다. 다음은 정렬할 배열의 초기값 입니다.

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

sorted(by:) 메소드는 배열의 컨텐츠와 동일한 타입의 두개의 인자를 받는 클로져 받고, 첫번째 값이 두번째 값의 이전 또는 이후에 표시되야 하는지에 대한 Bool 값을 반환합니다. 정렬하는 클로져는 첫번째 값이 두번째 값 앞(before)에 나타나면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"]

첫번재 문자열(s1)이 두번째 문자열(s2)보다 더 크면, backward(_:_:) 함수는 true를 반환할 것이며, 정렬된 배열에서 s1은 s2 앞에 나타날것입니다. 문자열에서의 문자들이, 더 크다(greater than)는 알파벳이 나중에 나타난다를 의미합니다. 이는 "B" 글자가 "A"글자보다 더 크다(greater than)를 의미하고, "Tom".문자열은 "Tim"문자열보다 더 크다는 것을 의미합니다. 알파벳을 역순으로 정렬하면 "Barry"는 "Alex"앞에 위치하게 됩니다.

하지만, 본질적으로(essentially) 단일 표현식 함수(a > b)를 작성하는 것은 오히려 장황한(long-winded) 방법입니다. 이 예제에서, 클로져 포현 문법을 사용해서, 정렬하는 클로져를 인라인으로 작성하는것이 바람직합니다.

클로져 표현 문법(Closuer Expression Syntax)

클로져 표현 문법은 다음과 같은 형태입니다.

{ (parameters) -> return type in
    statements
}

클로져 표현 문법에서 매개변수(parameters)는 in-out 매개변수가 될수 있지만, 기본 값을 가질수는 없습니다. 가변(variadic) 매개변수는 가변 매개변수에 이름을 지정하는 경우에는 사용할수 있습니다. 튜플은 매개변수 타입과 반환 타입으로 사용할 수 있습니다.

아래 예제는 위의 backward(_:_:) 함수의 클로져 표현을 보여줍니다.

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

인라인 클로져로 매개변수와 반환 타입의 선언한 것은 backward(_:_:) 함수의 선언과 동일한 것을 주의합니다. 두 경우 모두, (s1: String, s2: String) -> Bool으로 작성됩니다. 하지만, 인라인 클로져 표현에 대해, 매개변수와 반환 타입은 중괄호(`cury braces) 바깥쪽(outside)이 아니라, 안쪽(inside)에 작성됩니다.

클로져의 본문의 시작은 in 키워드에 의해 시작합니다. 이 키워드는 클로져의 매개변수와 반환 타입 정의가 완료되었음을 나타내고, 클로져의 본문은 시작됩니다.

클로져의 본문이 매우 짧기 때문에, 한줄로 작성할 수 있습니다.

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

이는 sorted(by:) 메소드에 대한 모든 호출이 동일하게 유지되는 것을 의미합니다. 괄호 쌍은 여전히 메소드의 전체 인자를 감싸고(wrap) 있습니다. 하지만, 그 인자는 이제 인라인 클로져 입니다.

문맥에서 타입 추론하기(Inferring Type From Context)

메소드에 인자처럼 정렬하는 클로져(sorting closure)가 전달되기 때문에, Swift는 매개변수의 타입과 반환하는 값의 타입을 추론할 수 있습니다. sorted(by:) 메소드는 문자열 배열에서 호출되며, 따라서 인자는 반드시 (String, String) -> Bool 타입의 함수여야 합니다.(String, String)과 Bool 타입은 클로져 표현식 정의의 일부로 작성할 필요가 없다는 것을 의미합니다. 모든 타입을 추론 할수 있기 때문에, 반환 화살표(->)와 괄호(parentheses) 근처의 매개변수(parameters)들을 생략할 수 있습니다.

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

클로져에 함수나 메소드를 인라인 클로져 표현식으로 전달할때 매개변수 타입과 반환 타입을 추론하는 것이 항상 가능합니다. 결과적으로, 클로져가 함수나 메소드 인자로 사용될때에는 전체 형식(fullest form)의 인라인 클로져를 작성할 필요가 없습니다.

그럼에도 불구하고, 원한다면 타입을 명시적으로 작성할 수 있고, 코드를 읽을때 애매모호함을 피할수 있는경우에는 명시적으로 작성하는 것을 권장합니다. sorted(by:) 메소드의 경우에, 클로져의 목적은 사용하는 곳에서 정렬하는 사실을 명확히 하는 것이고, 클로져가 문자열 배열의 정렬을 도와주기 때문에, 클로져가 String 값으로 작업한다고 가정하는 것이 안전합니다.

단일 표현식 클로져에서 암시적으로 반환하기(Implicit Returns from Single-Expression Closures)

단일 표현식 클로져(Single-expression closures)는 이전 예제 버젼에서 처럼, 선언에서 return 키워드를 생략해서 단일 표현식 클로져의 결과를 암시적으로 반환할 수 있습니다.

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

여기에서, sorted(by:) 메소드 인자의 함수 타입은 클로져에서 Bool 값이 반드시 반환되어야 하는 것을 명확히 합니다. 클로져의 본문에 포함된 단일 표현식(s1 > s2)으로 Bool 값을 반환하기 때문에, 그것은 애매모호하지 않고, return 키워드는 생략될수 있습니다.

축약 인자 이름(Shorthand Argument Names)

Swift는 자동으로 인라인 클로져에 $0, $1, $2, 등등 의 이름으로 클로져의 인자의 값을 참조해서 사용할수 있는 축약(shorthand) 인자 이름을 제공합니다.

클로져 표현식에서 이러한 축약 인자 이름을 사용하는 경우에, 선언에서 클로져 인자 목록을 생략할 수 있고, 축약 인자 이름의 숫자와 타입은 예상된 함수 타입으로 부터 추론 될 것입니다. 클로져 표현식이 본문 전체로 구성되기 때문에, in 키워드 또한 생략될 수 있습니다.

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

여기에서, $0과 $1은 클로져의 첫번째와 두번째 String 인자를 참조합니다.

연산자 메소드(Operator Methods)

실제로 위의 클로져 표현식을 더 짧게(shorter) 작성하는 방법이 있습니다. Swift의. String타입은 두개의 String 타입의 매개변수와 Bool 타입의 반환 타입을 가진 메소드로 비교(greater-than) 연산자(>)의 문자열 고유의(string-specific) 구현을 정의합니다. 이는 sorted(by:) 메소드에서 필요한 메소드 타입과 정확히 일치합니다. 따라서, 간단하게 비교 연산자(greater-than operator)로 전달할 수 있고, Swift는 문자열 고유(string-specific)의 구현을 사용하길 원한다고 추론할 것입니다.

reversedNames = names.sorted(by: >)

연산자 메소드에 대한 자세한 것은, 연산자 메소드(Operator Methods)를 보세요.

후행 클로져(Traling Closures)

함수의 마지막 인자에 클로져 표현을 전달하고 클로져 표현이 긴 경우에, 후행 클로져(trailing closure)를 사용하는 것이 유용할 수 있습니다. 후행 클로져는 함수 호출하는 괄호(parentheses) 뒤에 작성되며, 함수에 인자로 사용됩니다. 후행 클로져 문법을 사용할때, 함수 호출하는 부분에서 클로져에 대한 인자 라벨(label)을 작성하지 않습니다.

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 Expression Syntax) 섹션에서 문자열 정렬(string-sorting) 클로져는 sorted(by:) 메소드의 괄호 바깥쪽에 후행 클로져로 작성할 수 있습니다.

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

클로져 표현식이 함수나 메소드에 하나의 인자를 제공하고 그 표현식이 후행 클로져가 제공되는 경우에, 함수를 호출할때 함수나 메소드의 이름 뒤의 괄호 쌍(()) 을 작성할 필요가 없습니다.

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

클로져가 한줄에 들어가지 않을 만큼 충분히 길때 후행 클로져가 가장 유용합니다. 예를들어, Swift의 Array 타입은 인자 하나로 클로져 표현식을 사용하는 map(_:)메소드가 있습니다. 그 클로져는 배열의 각 항목(item)마다 한번씩 호출되고, 항목에 대해 매핑된 값(아마도 다른 타입)을 반환합니다. 매핑(mapping)의 본질(nature)과 반환되는 값의 타입은 클로져에 지정되어 있습니다.

제공된 클로져를 각 배열 요소에 적용한 후에, map(_:) 메소드는 원래 배열과 동일한 순서로 새로 맵핑된 값을 포함한 새로운 배열을 반환합니다.

다음은 map(_:)메소드를 Int 배열을 String 값의 배열로 변환하기 위해 후행 클로져를 사용하는 방법입니다. [16, 58, 510]배열은 새로운 ["OneSiz", "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]

위 코드는 정수 숫자와 영어 이름 버젼을 매핑한 딕셔너리를 생성하였습다. 또한, 정수 배열을 정의하였으며, 문자열로 변환할 준비가 되었습니다.

이제 후행 클로저로 배열의 map(_:) 메소드로 클로져 표현식을 전달해서, String 값의 배열을 만들기 위해 numbers 배열을 사용할 수 있습니다.

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의 마지막 숫자(digit)를 계산하고, digitNames 딕셔너리에서 이 숫자(digit)와 어울리는 문자열을 찾습니다. 그 클로져는 0보다 큰 정수의 문자열 표현식을 만드는데 사용할 수 있습니다.

주의
딕셔너리의 키가 존재하지 않으면 검색이 실패할수 있다는 것을 나타내기 위해 딕셔너리 서브스크립트는 옵셔널 값을 반환하기 때문에, digitNames 딕셔너리의 서브스크립트(subscript: 첨자)에 대한 호출에 느낌표(!)를 붙입니다. 위 예제에서, number % 10은 항상 digitNames 딕셔너리에 대한 유효한 서브스크립트 키를 보장하고, 느낌표 표시(exclamation mark)는 서브스크립트의 옵셔널 반환 값을 저장한 String 값을 강제 언래핑(force-unwrap)하기 위해 사용됩니다.

digitNames 딕셔너리에서 가져온 문자열은 output의 앞(front)에 추가되며, 역순의 문자열 버젼을 실제로 만들어줍니다. (number % 10 표현식은 16에 대해서 6을, 58에 대해서 8을, 510에 대해서 0의 값을 줍니다.)

number변수는 10으로 나눕니다. 정수형이기 때문에, 나누기 중에 16은 1로, 58은 5로, 510은 51이 됩니다.

이러한 과정은 number가 0이 될때까지 반복되며, output문자열은 클로져에 의해 반환되고, map(_:) 메소드에 의해 output 배열에 추가됩니다.

위 예제에서 후행 클로져 문법을 사용하여 map(_:) 메소드의 바깥쪽 괄호로 전체 클로져를 감쌀 필요없이, 클로져를 지원하는 함수 바로 뒤에 클로져의 함수를 깔끔하게 캡슐화 합니다.

값 캡쳐하기(Capturing Values)

클로져는 컨텍스트(context) 근처에 정의된 상수와 변수를 캡쳐(capture) 할 수 있습니다. 클로져는 이러한 상수와 변수를 본문에서 참조하고 수정할 수 있으며, 정의된 상수와 변수의 원래 범위가 존재하지 않더라도 마찬가지 입니다.

Swift에서, 값을 캡쳐할수 있는 클로져의 가장 간단한 형식은 중첩된 함수이며, 다른 함수의 본문에 작성합니다. 중첩된 함수는 바깥쪽 함수의 인자들을 캡쳐할수 있고 바깥쪽 함수에서 정의된 모든 상수와 변수들까지 캡쳐할 수 있습니다.

다음은 중첩된 함수 incrementer를 포함한, makeIncrementer이라는 함수 예제입니다. 중첩된 incrementer()함수는 컨텍스트(context) 주변으로부터 runningTotal과 amount 두개의 값을 캡쳐합니다. 이러한 값들을 캡쳐하고 나서, incrementer는 makeIncrementer에 의해 호출될때마다 amount만큼 runningTotal을 증가시키는 클로져로 반환됩니다.

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

makeIncrementer의 반환 타입은 () -> Int입니다. 이는 단순한 값 대신에 함수(function)를 반환하는 것을 의미합니다. 반환하는 함수는 매개변수가 없으며, 호출될때마다 Int값을 반환합니다. 함수가 다른 함수를 반환하는 방법을 배우려면, 반환타입으로 사용하는 함수 타입(Function Types as Return Types)을 보세요.

makeIncrementer(forIncrement:)함수는 현재 실행중에 증가하는 합계를 저장하고 반환하기 위해 정수형 변수 runningTotal을 정의하였습니다. 이 변수는 0값으로 초기화되었습니다.

makeIncrementer(forIncrement:) 함수는 forIncrement 인자 라벨과 amount 매개변수 이름을 가진 하나의 Int 매개변수를 가지고 있습니다. 인자 값은 전달 됩니다. 이 매개변수에 전달된 인자 값은 반환된 증가 함수에 의해 runningTotal을 매번 어느정도 증가시켜야 하는지를 지정합니다. makeIncrementer 함수는 실제로 증가를 수행하는 중첩된 함수 incrementer를 정의합니다. 이 함수는 단순히 runningTotal에 amount를 추가하고, 결과를 반환합니다.

분리되어 있다고 생각할대, 중첩된 incrementer() 함수는 이상하게 보일것입니다.

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

incrementer()함수는 어떠한 매개변수도 없고, 함수 본문에서 runningTotal과 amount를 참조하고 있습니다. 주변 함수로부터 runningTotal과 amount에 대한 참조(reference)를 캡쳐하고 자체 함수 본문에서 사용합니다. 참조로 캡쳐하면 makeIncrementer 호출이 끝났을때에도 runningTotal과 amount가 사라지지 않고, runningTotal은 다음번에 incrementer 함수가 호출되더라도 사용할수 있다는 것을 보장합니다.

주의
최적화에서, Swift는 클로져에 의해 값이 변경되지 않으면 값을 캡쳐하는 대신에 복사(copy)해서 저장할 것이고, 클로져가 생성된 후에는 그 값은 수정되지 않습니다.

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

두번째 incrementer를 생성하는 경우, 자체적으로 새로 저장된 참조를 가질 것이며, runningTotal 변수와는 구분됩니다.

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

원래 incrementer(incrementByTen)을 다시 호출하면 자신만의 runningTotal 변수를 계속 증가시키고, incrementBySeven에 의해 캡쳐된 변수에는 아무런 영향이 없습니다.

incrementByTen()
// returns a value of 40

주의
클래스 인스턴스의 프로퍼티에 클로져를 할당하고, 클로져는 인스턴스나 멤버를 참조하는 인스턴스를 캡쳐하는 경우에, 클래스와 인스턴스간에 강한 순환 참조가 생성될 것입니다. Swift는 강한 순환참조를 깨기 위해, 캡쳐 목록(capture lists)을 사용합니다. 더 자세한 정보는 클로져에 대한 강한 순환 참조(Strong Reference Cycles for Closures)를 보세요.

클로져는 참조 타입 입니다(Closures Are Reference Types)

위 예제에서, incrementBySeven과 incrementByTen은 상수이지만, 이 상수를 참조한 클로져는 여전히 runningTotal 변수를 증가할 수 있습니다. 이는 함수와 클로져가 참조 타입(reference types)이기 때문입니다.

함수나 클로져를 상수나 변수에 할당 할 때마다, 실제로 함수나 클로져를 참조(reference)하는 상수와 변수를 설정합니다. 위 예제에서,incrementByTen은 상수를 참조(refers)하는 클로져를 선택하고, 클로져 자신의 내용(contents)은 아닙니다.

이는 클로져에 두개의 다른 상수와 변수를 할당하는 경우에, 이러한 상수나 변수 모두 동일한 클로져를 참조할 것을 의미합니다.

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

클로져 탈출하기(Escaping Closures)

클로져가 함수에 인자로 전달될때 클로져는 함수를 탈출한다(escape)고 말하지만, 함수가 반환한 뒤에 호출됩니다. 클로져를 매개변수중의 하나로 사용하는 함수를 선언할때, 클로져가 탈출하기(escape) 를 허용한다는 것을 나타내기 위해 매개변수의 타입 전에 @escaping을 작성할 수 있습니다.

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

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

someFunctionWithEscapingClosure(_:) 함수는 인자처럼 클로져를 사용하고 함수 바깥쪽에 선언된 배열에 추가합니다. 함수의 매개변수가 @escaping으로 표시되지 않은 경우에, 컴파일 오류가 발생할 것입니다.

클로져가 @escapin으로 표시하는 것은 클로져에서 self를 명시적으로 참조하고 있다는 것을 의미합니다. 예를 들어, 아래 코드에서, 클로져는 someFunctionWithEscapingClosure(_:)에 전달되는 탈출하는 클로져(escaping closure)이며, 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"

자동클로져(Autoclosures)

자동클로져(autoclosure)는 클로져가 함수에 인자로 전달될때 표현식을 감싸기 위해 자동적으로 생성됩니다. 어떠한 인자도 없고, 호출될때, 내부에서 래핑된(wrapped) 표현식의 값을 반환합니다. 이 구문상의 편리함은 명시적인 클로져 대신에 일반 표현식을 작성해서 함수의 매개변수 주변의 괄호를 생력할수 있습니다.

일반적으로 함수 호출(call)은 자동클로져(autoclosures) 이지만, 이러한 종류의 함수를 구현(implement)하는 것은 일반적이지 않습니다. 예를 들어, assert(condition:message:file:line:)함수는 condition과 message 매개변수에 대해 자동클로져(autoclosure)를 가집니다. 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"

클로져 내부의 코드에 의해 customersInLine 배열의 첫번째 요소가 제거되더라도, 배열 요소는 클로져가 실제 호출되기 전까지 제거되지 않습니다. 클로져가 호출된적이 없다면, 클로져 안의 표현식을 처리되지 않으며, 배열 요소가 결코 제거되지 않는다는 것을 의미합니다. 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 속성으로 표시하여 자동클로져를 사용합니다. 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!"

주의
자동클로져를 남용하게 되면 코드를 이해하기 어려워집니다. 컨텍스트(context)와 함수 이름은 처리가 지연된다는 것을 명확히 해야합니다.

탈출(escape)을 위해 자동클로져(autoclosure)를 원하는 경우에, @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 인자의 값은 함수의 범위를 탈출하기 위해 반드시 허용되어야 합니다.

참고
함수의 매개변수에 @autoclosure를 선언하면, 중괄호({})를 생략할수 있게 해준다.
callClouser({ code }) -> callClouser(code)


반응형

'Swift > Language Guide' 카테고리의 다른 글

Initialization  (0) 2018.09.17
Inheritance  (0) 2018.08.30
Subscripts  (0) 2018.08.30
Methods  (0) 2018.08.30
Properties  (0) 2018.08.30
Structures and Classes  (1) 2018.08.30
Enumerations  (0) 2018.08.30
Functions  (0) 2018.08.30
Posted by 까칠코더
,