[Swift] Closures (2)
Closures 2편이다.
1편을 보지 않았다면 꼭 보고 오는 걸 추천한다.
[Swift] Closures (1)
Swift에는 클로저(Closures)라는 개념이 있다. 하나의 코드 블럭이라고 생각하면 이해가 쉽다. { (parameters) -> return type in statements } 클로저는 일반적으로 위와 같은 형태를 가지고 있다. 클로저는 저장
littlemoom.tistory.com
캡처값(Capturing Values)
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer 안에 incrementer가 있다.
이제 makeIncrementer가 호출되면 내부의 runningTotal은 0으로 초기화가 된다.
하지만 아래처럼 상수 또는 변수로 설정하고 함수를 여러번 호출해보면,
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // returns a value of 10
incrementByTen() // returns a value of 20
incrementByTen() // returns a value of 30
runningTotal과 amount에 대한 참조가 캡처되어 값이 계속해서 증가하는 것을 볼 수 있다.
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() // returns a value of 7
incrementByTen() // returns a value of 40
새롭게 변수에 저장된다면, 다시 runningTotal이 0으로 초기화되어 저장된다.
이후 기존에 저장된 함수를 호출하더라도 저장된 각 클로저간에는 영향을 주지 않는다.
이 때문에 클로저와 인스턴스 사이에 강한 참조 사이클(Strong Reference Cycles)이 발생할 수 있는데,
이에 대한 것은 다른 글에서 알아보도록 하자.
클로저는 참조 타입(Closures Are Reference Types)
말 그대로 클로저는 참조 타입이다.
변수 또는 상수로의 저장 시, Reference Type으로 저장된다.
이스케이핑 클로저(Escaping Closures)
중요하다!
탈출하는 클로저 인데, @escaping 키워드를 붙인다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
해당 코드에서 someFunctionWithEscapingClosure 함수는
completionHandlers에 completionHandler를 append 하고 반환된다.
하지만 completionHandler는 escaping closures기 때문에
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething() // x is 200
completionHandlers.first?() // x is 100
이와 같이 나중에도 실행이 가능하다.
Escaping Closures에 self를 쓰면 캡처로 인한 강한 참조가 생기기 쉽다!
자동 클로저(Autoclosures)
클로저는 일반적으로 { } 중괄호에 싸여 있다.
하지만 자동 클로저 속성을 이용하면 중괄호의 생략이 가능하다.
// autoclosure 미적용
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// autoclosure 적용
func serveAutoclosure(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
serveAutoclosure(customer: customersInLine.remove(at: 0)) // 중괄호 생략 가능
물론 @autoclosure @escaping을 동시에 사용할 수도 있다.
Autoclosures의 경우, 다른 사람 또는 미래의 내가 코드를 이해하기 어려워질 수도 있기 때문에 사용에 주의해야한다.