클로저는 코드블럭으로 람다와 비슷하다.
클로저는 어떤 상수나 변수의 참조를 캡쳐해 저장할 수 있다.
클로저는 다음 세 가지 형태 중 하나를 갖는다.
인라인 클로저를 명확하게 표현하는 방법의 문법에 초점이 맞춰져 있다.
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"]
{ (parameters) -> return type in
statements
}
함수로 따로 정의된 형태가 아닌 인자로 들어가 있는 형태의 클로저를 인라인 클로저라 부른다.
클로저의 인자 값과 반환 타입을 생략할 수 있지만, 가독성과 코드의 모호성을 피하기 위해 타입을 명시할 수 도 있다.
단인 표현 클로저에서는 return을 생략할 수 있다.
names.sorted(by: {s1, s2 in s1 > s2})
names.sorted(by: {$0 > $1})
names.sorted(by: >)
함수의 마지막 인자로 클로저를 넣고 그 클로저가 길다면 후위 클로저를 사용할 수 있다.
reversedNames = names.sorted() { $0 > $1 }
클로저는 특정 문맥의 상수나 변수의 값을 캡쳐할 수 있기 때문에 원본 값이 사라져도 클로져의 body 안에서 그 값을 활용할 수 있다.
Swift에서 값을 캡쳐하는 가장 단순한 형태는 중첩함수이다.
중첩함수는 함수의 body에서 다른 함수를 다시 호출하는 형태로 된 함수이다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
(forIncrement amount: Int) 부분이 인자값
() -> Int 반환값 (반환값이 클로저인 형태)
함수를 여러번 실행하게 되면 변수를 캡쳐링해서 공유하기 때문에 계산이 누적되어진다.
새로운 클로저를 생성시에는 별개의 클로저이기 때문에 다른 값이 나오게 된다.
함수와 클로저는 참조 타입이기 때문에 상수나 변수에 할당할 때 실제로는 상수와 변수에 해당 함수나 클로저의 참조가 할당된다.
한 클로저를 두 상수나 변수에 할당하면 그 두 상수나 변수는 같은 클로저를 참조하고 있다.
함수가 끝나고 실행되는 클로저, 예를들어 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 타입 앞에 @escaping 를 명시해야 한다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
@escaping 을 사용하는 클로저에서는 self를 명시해줘야한다.
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
인자값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저이다.
자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않는다. 따라서 복잡한 연산을 할 때 유용하다. (실제 사용될 때 지연 호출 된다.)
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"
@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!"