Closure 표현식 은 간결하고 집중된 구문으로 인라인 Closure를 작성하는 방법입니다
. 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를 인라인으로 작성하는 것이 좋습니다.
{ (parameters) -> return type in
statements
}
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
reversedNames = names.sorted(by: { $0 > $1 } )
reversedNames = names.sorted(by: >)
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를 사용하는 대신 성공적인 다운로드 후 사용자 인터페이스를 업데이트하는 코드에서 네트워크 오류 처리를 담당하는 코드를 명확하게 분리할 수 있습니다.
특히 여러 핸들러를 중첩해야 하는 경우 완료 핸들러를 읽기 어려울 수 있습니다. 다른 접근 방식은 동시성에 설명된 대로 비동기 코드를 사용하는 것입니다.
중첩 함수는 외부 함수의 모든 매개변수를 캡처할 수 있으며 외부 함수 내에 정의된 모든 상수 및 변수도 캡처할 수 있습니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
최적화를 위해 Swift는 해당 값이 클로저에 의해 변경되지 않고 클로저가 생성된 후 값이 변경되지 않은 경우 대신 값의 복사본을 캡처하고 저장할 수 있습니다.
또한 Swift는 더 이상 필요하지 않을 때 모든 메모리 관리를 처리합니다.
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7
incrementByTen()
// returns a value of 40
클래스 인스턴스의 속성에 클로저를 할당하고 클로저가 인스턴스 또는 해당 멤버를 참조하여 해당 인스턴스를 캡처하는 경우 클로저와 인스턴스 사이에 강력한 참조 주기를 생성합니다.
위의 예에서 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를 참조하기 때문에 둘 다 동일한 누계를 증가시키고 반환합니다
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 }
}
}
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
자동 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 인수의 값은 함수의 범위를 벗어나도록 허용되어야 합니다.