iOS/Swift

[Swift] Closures (2)

_Rey 2022. 5. 10. 01:09

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의 경우, 다른 사람 또는 미래의 내가 코드를 이해하기 어려워질 수도 있기 때문에 사용에 주의해야한다.