Closures
Swift에서의 클로저는 코드 블럭과 거의 동일한, 실행 가능한 코드 조각 객체이다. 클로저는 문맥에 따라 주위의 변수들을 캡쳐하여 그 사본을 내부에 저장한다. 함수나 네스팅된 함수는 모두 클로저의 특별한 형태이다.
let myClosure = { (a: Int, b: Int) -> Int in return a + b}
클로저는 그 자체가 문법상 중괄호로 둘러싸져 있으므로 파라미터와 타입시그니처를 그 내부에 작성한다. 여기서 in
문법이 쓰인다. 타입을 추론할 수 있는 경우에는 타입 명시가 필요 없으며, sort
함수의 인자로 쓰이는 등
, 파라미터 개수와 타입이 추론 가능한 경우 그 마저도 생략할 수 있다.
reversed = sort(names){ $0 > $1}
참고로 함수 인자로 전달하는 클로저의 내용이 긴 경우, 함수뒤의 괄호 내부 대신, 닫는 괄호 다음으로 클로저를 쓸 수 있다. (trailing closure)
특수한 경우지만 극단적으로는 >
연산자는 (T, T) -> Bool의 infix
형 함수라고 볼 수 있으므로
reversed = sort(names, >)
라고 쓰는 것도 가능하다.
캡쳐링
클로저는 문맥에 맞게 클로저 외부의 값을 캡쳐한다. 그리고 캡쳐된 값은 클로저의 연관된 값으로 내부에 저장된다. 다음 코드는 함수를 리턴하는 함수이다.
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let stepTen = makeIncrementor(forIncrement: 10)
stepTen()
// --> 10
stepTen()
// --> 20
네스팅된 함수 incrementor
는 그 내부에서 그를 감싼 함수의 로컬 변수인 runningTotal
을 참조한다. stepSeven
에 할당되는 클로져에서 내부에서 runningTotal
은 그 참조점이 없어질까? 자신을 생성한 함수의 실행은 종료되었으므로, 호출 스택에서 makeIncrementor
의 정보는 사라진다. 하지만 runningTotal
의 값은 생성 시점에 캡쳐링되어 저장된다. 이상하게 보이지만 이 값은 클로저 내에 계속해서 유지되며, stepSeven
이 반복해서 호출될 때 계속해서 증가하는 값을 가져오게 된다.
let stepSeven = makeIncrementor(forIncrement: 7)
stepSeven()
// --> 7
stepSeven()
// --> 14
또한 makeIncrementor
에 의해 생성되는 클로저의 인스턴스는 각각 개별적인 값을 캡쳐링한다.
stepSeven()
// --> 21
stepTen()
// --> 30
마지막으로 클로저는 레퍼런스 타입이다. 클로저가 다른 변수에 할당되거나, 함수의 인자로 전달될 때 새로운 사본이 생기지 않는다. (반대로 구조체와 열거는 값 타입이다.)