// 클로저는 전달하거나 사용할 수 있는 코드 블록이다.
let closureAsCode = {
let player1 = "Player1"
let computer = "Computer"
if player1 > computer {
print("\(player1) wins!")
} else {
print("Game Over")
}
}
closureAsCode()
// 클로저는 변수에 할당할 수 있다.
let closureAsVariable = { print ("I am a closure") }
// 함수의 코드 블록도 클로저이다.
func printer(payload: String) { // Function
print(payload) // Closure
}
// 함수의 파라미터로 클로저를 전달할 수 있다.
func printIt(closure: (String) -> Void) {
let message = "Hello old friend!"
closure(message)
}
printIt(closure: printer)
// 클로저는 타입이기도 하다.
// 인풋이 없고 String을 리턴하는 클로저 타입 : () -> (String)
var hello: () -> (String) = {
return "Hello!"
}
hello() // Hello!
// 타입을 지정해 주지 않아도 스위프트가 알아서 타입을 추론한다.
// 위의 코드와 똑같은 () -> (String) 타입을 가지고 있다.
var hello2 = {
return "Hello!"
}
hello2() // Hello!
// 정수를 인풋으로 받아서 정수를 리턴하는 클로저
var double: (Int) -> (Int) = { x in
return 2 * x
}
double(2) // 4
// 클로저를 다른 변수에 전달할 수 있다.
var alsoDouble = double
alsoDouble(3) // 6
클로저는 스위프트에서 변수, 함수의 파라미터, 리턴 값으로 전달할 수 있으므로 일급 객체(first class citizen)이다.
// func sorted(Element, Element) -> Bool) -> [Element]
// 배열을 정렬할 때 사용하는 sorted 메소드의 파라미터로 클로저를 전달할 수 있다.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
// sorted 파라미터 타입에 맞는 함수를 정의하고
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2 // define our logic (as a closure)
}
// sorted 파라미터에 전달할 수 있다.
let reversedNames1 = names.sorted(by: backward)
// 스위프트는 클로저를 간단하게 작성하게 하는 shortcuts를 가지고 있다.
// 함수를 정의하여 전달할 필요 없이 바로 클로저를 전달할 수 있다.
let reversedNames2 = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
// 스위프트가 타입을 추론할 수 있기 때문에 타입을 명시하지 않아도 된다.
let reversedNames3 = names.sorted(by: { s1, s2 in return s1 > s2 } )
// return 키워드를 생략할 수 있다.
let reversedNames4 = names.sorted(by: { s1, s2 in s1 > s2 } )
// 파라미터 이름을 생략할 수 있다.
// 첫번째 파라미터는 $0, 두번째 파라미터는 $1
let reversedNames5 = names.sorted(by: { $0 > $1 } )
// Trailing closure
// 함수의 마지막 인자가 클로저라면 중괄호를 밖으로 뺄 수 있다.
let reversedNames7 = names.sorted() { $0 > $1 }
// String의 연산자 메소드를 사용해 다음과 같이 표현할수도 있다.
let reversedNames6 = names.sorted(by: >)
클로저를 축약하면 코드가 짧아지지만 가독성이 떨어진다.
클로저는 기본적으로 주변의 상수나 변수를 강한 참조로 캡쳐한다. 캡쳐 리스트를 사용하면 클로저가 값이 캡쳐되는 방법을 정의할 수 있다. 캡쳐 리스트에 있는 변수나 상수는 클로저 안에서 상수로 초기화된다. 캡쳐 리스트를 사용할 때는 in 키워드를 사용해야 한다.
var a = 0
var b = 0
let closure = { [a] in
print(a, b)
}
a = 10
b = 10
closure()
// Prints "0 10"
위의 코드에서 변수 a와 클로저 안의 상수 a 그래서 a는 총 2개가 있다. 클로저가 생성될 때 클로저 안의 상수 a는 변수 a의 값을 캡처하지만 두 값은 연결되어있지 않다. 그렇기 때문에 a의 값을 10으로 변경했어도 클로저를 실행했을 때의 값은 0으로 처음에 초기화된 값으로 나온다. b는 변수b 하나밖에 없기 때문에 클로저 밖에서 값을 바꿔도 그대로 값이 적용된다.
하지만 캡쳐하는 변수가 참조 타입일 때는 같은 인스턴스를 참조하기 때문에 당연히 값을 바꿔도 그대로 적용된다.
class SimpleClass {
var value: Int = 0
}
var x = SimpleClass()
var y = SimpleClass()
let closure = { [x] in
print(x.value, y.value)
}
x.value = 10
y.value = 10
closure()
// Prints "10 10"
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0 // 변수 정의
func incrementer() -> Int {
runningTotal += amount // 클로저 블록 밖에 있는 runningTotal을 캡처하여 사용!
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30
// 클로저는 참조 타입이다.
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 40
incrementByTen() // 50
클로저는 기본적으로 non-escaping 클로저이다. 즉 파라미터로 클로저를 전달받는 함수를 호출할때 즉시 실행되는 클로저를 얘기한다. 본체 함수가 끝나고도 나중에 실행되거나 변수에 저장되는 클로저의 경우 @escaping을 붙여야한다. 클로저가 옵셔널일때는 디폴트로 @escaping이 적용된다.
클로저를 파라미터로 받는 함수를 호출할 때 클로저의 중괄호를 생략하게 해준다.