iOS/Swift

[Swift] 클로저 (2) - Capturing Values, Escaping Closure, Auto Closure

겸둥이xz 2021. 6. 21. 22:05
반응형

 

클로저의 값 캡쳐(Capturing Values)

  • 클로저는 참조 타입(Reference Type)입니다.
  • 클로저가 매개변수나 지역변수가 아닌 주변 외부의 context를 사용하기 위해 주변 외부의 context를 참조하는 것
  • 원본 값이 사려져도 클로저의 body 안에서 그 값을 활용할 수 있습니다.

 

예제 코드

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
  var runningTotal = 0
  func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
  }
  return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementBySeven = makeIncrementer(forIncrement: 7)

// runnigTotal과 amount가 캡쳐되서 그 변수를 공유하기 때문에 누적된 결과를 가집니다.
let plusedTen = incrementByTen() // 10
let plusedTen2 = incrementByTen() // 20

// 다른 클로저이기 때문에 고유의 저장소에 runningTotal과 amount를 캡쳐해서 사용합니다.
let plusedSeven = incrementBySeven() // 7
let plusedSeven2 = incrementBySeven() // 14
  • makeIncrementer 함수는 Int 타입 매개변수를 갖고, () -> Int 타입의 클로저를 반환합니다.
  • makeIncrementer 내부에는 incrementer라는 함수가 클로저로 반환되고 있습니다.
  • incrementerrunningTotalamount를 캡쳐해서 사용하고 있습니다.
한 클로저를 두 상수나 변수에 할당하면 두 상수 또는 변수는 같은 클로저를 참조하게 됩니다.
이렇게 Capturing by reference 하면 makeIncrementer에 대한 호출이 종료될 때 runningTotalamount가 사라지지 않으며 다음에 Incrementer 함수가 호출될 때 runningTotal을 사용할 수 있습니다.

 

만약 클로저를 어떤 클래스 인스턴스 프토퍼티로 할당하고, 그 클로저가 해당 인스턴스를 캡쳐하면 강한 순환 참조(Strong Reference Cycles)에 빠지게 됩니다. 인스턴스의 사용이 끝나도 메모리를 해제하지 못하게 되는 현상입니다. 그래서 Swift는 이 문제를 다루기 위해서 캡쳐 리스트를 사용합니다.

 

 

Escaping Closure

  • 클로저가 함수의 인자로 전달되지만 함수 밖에서 실행되는 것(함수가 반환된 후 실행됨)을 Escaping이라고 합니다.
  • 이러한 경우 매개변수 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.
  • 비동기로 실행되거나 completionHandler(완료에 따른 처리)로 사용되는 클로저의 경우 자주 사용됩니다.
클로저의 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
  • somFunctionWithEscapingClosure에서 인자로 전달된 completionHandler는 함수가 끝나고 나중에 처리됩니다.
  • 만약 함수가 끝나고 실행되는 클로저에 @escaping 키워드를 붙이지 않으면 컴파일 오류가 발생합니다.
  • @escaping을 사용하는 클로저에서는 self를 명시적으로 언급해야 합니다.

 

 

Auto Closure

  • 자동 클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다.
  • 자동 클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다.
  • 실제 계산이 필요할 때 호출이 되기 때문에 계산이 복잡한 연산을 하는데 유용합니다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count) // 5

let customerProvider = { customersInLine.remove(at: 0) } // 해당 코드가 지나도 count가 줄지 않습니다. Auto Closure
print(customersInLine.count) // 5

// customerProvider가 실행되었을때만 동작 (실제 실행)
print("Now serving \(customerProvider())!") // "Now serving Chris!"
print(customersInLine.count) // 4


// customersInLine : ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
// 자동 클로저를 함수의 인자 값으로 사용
serve(customer: { customersInLine.remove(at: 0) } ) // "Now serving Alex!"

 

@autoclosure 키워드를 이용해서 간결하게 표현 가능합니다.

// customersInLine : ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
  print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0)) // "Now serving Ewa!"

 

 

 

 

<참고자료>

https://medium.com/@jgj455/%EC%98%A4%EB%8A%98%EC%9D%98-swift-%EC%83%81%EC%8B%9D-closure-aa401f76b7ce

https://velog.io/@kimdo2297/%ED%81%B4%EB%A1%9C%EC%A0%B8-%EC%BA%A1%EC%B3%90%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-about-closure-capture

반응형