클로저가 함수의 파이널 아규먼트로 사용되거나 너무 길다면 트레일링 클로저(trailing closure
)를 사용할 수 있다.
트레일링 클로저는 함수 호출 시 괄호 다음에 작성한다. 이때 함수 호출의 일부로 첫 번째 클로저에 대한 아규먼트 라벨을 쓰지 않아도 된다. 함수 호출은 여러 개의 트레일링 클로저를 포함할 수 있다.
func func_closure(closure: () -> Void){
// function body
// function without trailing closure
}
func func_closure2(closure: {
// closure body
})
func func_trailing(){
// trailing closure body
}
트레일링 클로저가 메소드 괄호 바깥에 사용되는 경우는 다음과 같다.
reversedNames = names.sorted() { $0 > $1 }
이때 클로저 표현이 유일한 아규먼트고 이 표현을 트레일링 클로저로 쓸 때에는 괄호를 쓰지 않아도 된다.
reversedNames = names.sorted { $0 > $1 }
싱글 라인에 클로저를 쓰기 너무 길 때 트레일링 클로저를 사용하자.
배열의 Array.map(_:)
메소드가 싱글 아규먼트로 클로저 표현을 받는데, 배열 내 각 아이템에 대해서 한 번씩 호출되고 매핑 타입에 맞는 데이터를 리턴한다. 클로저를 통해 어떤 값을 리턴받을 지 전할 수 있다. 이 클로저가 모든 배열 내 원소에 적용된 이후에는 Array.map(_:)
메소드가 새로운 타입으로 매핑된 값이 담긴 새로운 배열을 리턴한다.
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]
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
}
map
메소드는 배열에 저장된 아이템 하나마다 클로저 표현을 호출한다.
타입 추론을 통해서 이후 매핑된 값이 저장될 배열이 어떤 타입인지도 알 수 있다.
위에서 digitNames
에 담긴 옵셔널 값을 강제 언래핑하는 것도 체크하자.
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
이 함수는 클로저가 두 개다. 첫 번째 클로저는 completion
핸들러로 다운로드가 성공적인 경우, 두 번째 클로저는 onFailure
핸들러로 에러가 발생했을 때를 다룬다.
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
loadPicture
를 호출했을 때 다운로드한 사진을 someView.currentPicture
로 할당하고, 실패할 경우 에러 핸들러에 전달한 프린트 문을 출력한다. 즉 함수 내 선언과 별개로 원하는 값을 출력할 수 있다는 게 특징이다.
클로저는 클로저가 정의된 콘텐스트에서 상수 및 변수를 캡처할 수 있다. 그리고 클로저 바디에서 정의된 기존 범위가 존재하지 않더라도 그 값을 참조 및 변경할 수 있다.
값을 캡처하는 가장 간단한 클로저는 중첩 함수다. 중첩 함수는 자신을 둘러싼 함수의 아규먼트, 변수, 상수를 모두 캡처할 수 있다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer
의 리턴 타입은 () -> Int
, 즉 함수를 리턴하는데, 파라미터가 없고 정수 값을 리턴하는 함수다. 또한 이 함수는 forIncrement
이라는 아규먼트 라벨을 가진 amoun
라는 정수를 파라미터로 받는다.
makeIncrementer
는 incrementer
를 바깥에서 둘러싸고 있는데, 이때 incrementer
중첩 함수가 makeIncrementer
의 아규먼트 amount
와 변수 runningTotal
을 모두 캡처하고 있다.
이때 중첩 함수 incrementer()
가 파라미터 없이 선언되지만 자신을 둘러싼 makeIncrementer
값을 참조할 수 있다. 또한 makeIncrementer
호출이 끝나도 runningTotal
이나 amount
가 사라지지 않고 남아 있다는 데 주의.
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
amount
가 10으로 고정된 incrementByTen
은 호출될 때마다 amount
를 더한 10, 20, 30...
을 리턴하고 있다. 파라미터를 다르게 준 incrementBySeven
이 만들어진 이후에도 incrementByTen
이 그대로 유지되고 있다.