명명된 함수 생성없이 실행되는 코드 그룹이다. 익명함수와 유사하다.이 코드 블록은 변수나 함수의 인자로 전달될 수 있고 실행도 가능하다.
(클로저가 함수보다 더 큰 개념인거 같다.. 함수 == 이름있는 클로저 라고 생각하는게 맞을듯)
{ (parameters) -> ReturnType in
// 클로저의 코드
}
클로저의 본문의 시작은 in 키워드로 시작한다. 클로저의 파라미터는 기본값을 가질 수 없다.
클로저를 인수로 사용할 수 있는 함수를 고차 함수라고 한다.
- map : 배열의 각 요소에 대해 클로저를 적용하여 새로운 값을 반환한다.
- filter : 배열의 요소 중 조건을 만족하는 요소들로 새로운 배열을 만든다.
- sorted : 배열을 정렬하는데 사용되며, 클로저를 통해 정렬 기준을 제공할 수 있다.
- reduce : 배열의 모든 요소를 결합하여 단일 값으로 반환한다.
- forEach : 배열의 각 요소에 대해 클로저를 순회하며 작업을 수행한다.
- compactMap : 배열의 각 요소에 대해 클로적를 적용하여 옵셔널 값이 아닌 요소들로만 이루어진 배열을 반환한다.
클로저의 대표적인 예시인 정렬 메소드의 예시는 다음과 같다.
let numbers = [5,2,7,1,9]
let sortedNumbers = numbers.sorted(by: { (num1, num2) -> Bool in return num1 < num2})
sorted의 인수는 인라인 클로저인 셈이다.
sorted(by:) 함수는 Swift 표준 라이브러리에 있는 배열에서 사용할 수 있는 메서드 중 하나다. 이이 메서드는 배열의 요소를 정렬하여 새로운 배열을 반환한다. 정렬방법은 사용자가 제공하는 클로저에 의해 결정된다.
정렬클로저는 메서드의 인수로 전달되기 때문에 swift는 파라미터 타입과 반환되는 값의 타입을 유추할 수 있다. (Red Textswift의 타입추론기능, 컴파일러가 클로저 뿐만 아니라 제공된 메서드의 형식을 살펴보기 때문..그렇지만 왠만하면 생략하지 않는게 좋을 듯 하다.) 그렇기 때문에 파라미터의 이름을 둘러싼 소괄호와 반환 화살표를 생략할 수 있다.
let numbers = [5,2,7,1,9]
let sortedNumbers = numbers.sorted(by: {num1, num2 in return num1 > num2})
인라인 클로저에서는 인수값을 참조하는데 사용할 수 있는 짧은 인수 이름을 제공한다. 짧은 인수이름을 사용한다면 인수선언을 생략할 수 있다. (마찬가지로 함수에 의해 유추되는것) 클로저가 본문으로만 구성되는 것이기 때문에 in 키워드를 생략할 수 있다ㅏ.
let sortedNumbers = numbers.sorted(by: {$0 > $1})
이때 > 연산자 또한 메서드이고 이 메서드는 string 타입의 인수를 2개 받았을때 bool값을 리턴해주고 이는 sorted 메서드에 필요한 메서드 타입과 정확하게 일치하기 때문에 다음과 같이 사용할 수 있다.
let sortedNumbers = numbers.sorted(by: >)
선행 클로저는 함수 호출 시 클로저는 직접 인자로 전달하였다면 후행 클로저는 함수 호출 시 클로저를 마지막 인자로 전달하는 방식이다. 후행 클로저는 클로저가 길어서 한줄로 인라인으로 작성이 불가능할 때 유용하다.
let sortedNumbers = numbers.sorted() { (num1, num2) -> Bool in return num1 < num2}
후행 클로저로 표현식이 함수와 메서드의 유일한 인수일 경우 함수를 호출할 때에는 소괄호()를 작성하지 않아도 된다.
let sortedNumbers = numbers.sorted { $0 > $1 }
map메서드는 기존 배열에 해당값과 같은 순서로 새로 매핑된 값의 새로운 배열을 반환한다. map을 사용한 후행클로저의 예시는 다음과 같다.
let numbers = [11,15,473]
let digitNames = [ 0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
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
}
함수가 여러개의 클로저를 가지고 있을 수도 있다.
func performOperation(x: Int, y: Int, operation: (Int, Int) -> Int, completion: (Int) -> Void) {
let result = operation(x, y)
completion(result)
}
let addClosure = { (a: Int, b: Int) -> Int in
return a + b
}
let printClosure = { (result: Int) in
print(\(result))
}
performOperation(x: 5, y: 3, operation: addClosure, completion: printClosure)
클로저는 클로저가 포함된 블록의 상수와 변수를 캡쳐할 수 있다. 캡처값은 클로저가 생성될 때의 상태를 유지한다. 따라서 클로저 내에서 해당 변수나 상수의 값이 변경되더라도 클로저가 캡처한 값은 변경되지 않는다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() //10
incrementByTen() //20
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() //6
incrementByTen() //30
위의 함수의 반환타입은 함수(클로저)이다. 이 클로저는 runningTotal 값을 캡처하고, 호출될 때마다 값을 증가시키고 반환한다. 이러한 방식으로 클로저는 외부 범위에 있는 변수나 상수의 값을 기억하고 사용할 수 있다.
이때 주의할 점은 클로저가 캡처하는 변수나 상수는 참조를 통해 캡처되므로, 해당 변수나 상수의 값이 변경될 때 캡처한 값도 함께 변경된다는 것이다.😮 incrementByTen()을 여러번 호출하면 이전 값에서 10이 증가되는 것이 이런 이유 때문이다.
var closure1: () -> Void = {}
var closure2 = closure1
closure1 = {
print("Hello")
}
closure1() // 출력: "Hello"
closure2() // 출력: (출력 없음)