클로저는 코드 블록으로, C와 Objective-C의 블럭 또는 다른 언어의 람다(Lambda)와 비슷
{ (parameters) -> ReturnType in
statements
}
정렬 메서드로 알아보는 클로저의 축약
스위프트 표준 라이브러리에는
sorted(by:)
라는, 배열 값을 정렬하는 메서드가 존재
by
에 정렬 기준을 적은 클로저를 넣으면 그 기준대로 정렬이 된다.let someInts = [3, 2, 6, 4, 1, 5] func desc(_ lhs: Int, _ rhs: Int) -> Bool { return lhs > rhs } let descInts = someInts.sorted(by: desc) // descInts : [6, 5, 4, 3, 2, 1]
이 때
by
의 형태는(Int, Int) -> Bool
라는 것을 알 수 있다.
- 클로저 표현 문법
let descInts = someInts.sorted(by: { (lhs: Int, rhs: Int) -> Bool in return lhs > rhs })
- 문맥에서 타입 추론
someInts
에서 사용되는sorted(by:)
는 타입이(Int, Int) -> Bool
이라는 것을
알 수 있기 때문에, 타입을 생략하고 사용 가능let descInts = someInts.sorted(by: { lhs, rhs in return lhs > rhs })
- 단일 표현 클로저에서의 암시적 반환
단일 표현 클로저에서는return
키워드를 생략 가능let descInts = someInts.sorted(by: { lhs, rhs in lhs > rhs })
- 인자 이름 축약
클로저는 인자 값이 앞에서부터$0
,$1
, ... 으로 축약된
축약 인자 이름을 제공let descInts = someInts.sorted(by: { $0 > $1 })
- 연산자 메서드
Int
,String
등 해당 타입끼리 비교할 수 있는 비교 연산자가 있으므로, 그냥 연산자 사용 가능let descInts = someInts.sorted(by: >)
함수의 마지막 인자에 클로저가 있다면
클로저를 블록 형태로 사용하는 후위 클로저를 사용할 수 있다.
func functionWithClosure(closure: () -> Void) { ... }
functionWithClosure(closure: {
...
})
functionWithClosure() {
...
}
// 후위 클로저일 때 ()를 생략 가능
functionWithClosure {
...
}
클로저는 특정 컨텍스트의 상수 / 변수 값을 캡쳐할 수 있다.
원본 값이 사라져도 클로저 내에서 그 값을 활용 가능
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
이 때 incrementByTen
은 incrementer()
이라는 클로저(함수)를 반환함.
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30
runningTotal
과 amount
가 incrementer()
의 내부에 변수로 저장되어 있지 않지만, 계속 값이 증가한다.
이는 runningTotal
, amount
가 캡쳐링되어서 변수를 공유하기 때문
강한 참조 순환
추후 ARC에서 정리
Closure : Reference Type
위 예제에서, incrementByTen()
은 let
으로 선언되었지만
해당 함수(클로저)의 Reference가 할당되기 때문에 runningTotal
, amount
를 변경 가능
let anotherIncrementByTen = incrementByTen
anotherIncrementByTen() // 40
anotherIncrementByTen
또한 makeIncrementer(amount:)
의 참조를 가지게 되므로
기존 incrementByTen()
에서 만들어 놓았던 30
이란 값을 가지고 계산을 함.
@escaping
클로저를 함수의 파라미터로 넣을 때, 함수가 끝나고(함수 밖에서) 실행되는 클로저는 @escaping
키워드를 붙여야 함.
클로저가 옵셔널일 때는 붙이지 않는다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
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)
// 200
completionHandlers.first?()
print(instance.x)
// 100
@autoclosure
함수의 인자로 전달되는 코드를 감싸서, 자동으로 클로저로 만들어주는 키워드
전달되는 코드는 인자가 없고 반환값만 있어야 한다.
자동 클로저는 클로저를 호출하기 전까지 실행되지 않으므로,
복잡한 연산을 할 때 유용
하지만 예제는 간단한 연산
@autoclosure
없을 때func printAdds(_ closure: () -> Int) {
print("Add: \(closure())")
}
printAdds({ 2 + 3 })
@autoclosure
있을 때func printAdds(_ closure: @autoclosure () -> Int) {
print("Add: \(closure())")
}
printAdds( 2 + 3 ) // { } 사라짐
위에서 보는 것과 같이, @autoclosure
을 남용하면 코드를 이해하기 어려워지므로
적재적소에 사용해야 한다.
@escaping
이 필요한 경우 두 키워드 모두 붙여주어야 함
func function(_ closure: @autoclosure @escaping () -> String) {
...
}