본문 바로가기

Swift/Swift Language

Swift 언어 가이드 - 값 캡쳐링

값 캡쳐링

클로져는 주위 문맥으로부터 상수와 변수를 캡쳐(capture)할 수 있습니다. 이후 클로져는 이 상수와 변수를 클로져 내부에서 참조하거나 수정할 수 있습니다. 심지어 원본의 상수와 변수가 더 이상 존재하지 않더라도 그렇습니다.

스위프트에서 값을 캡쳐링하는 클로져의 가장 간단한 형태는 중첩 함수입니다. 이 함수는 다른 함수 내부에 작성된 함수입니다. 중첩 함수가 바깥 함수의 인자 중 아무 값이나 캡쳐할 수도 있으며 바깥 함수에서 정의된 상수나 변수를 캡쳐할 수도 있습니다.

여기에 makeIncrementer 라고 정의된 함수가 있습니다. 이 함수는 중첩 함수, incrementer 를 가지고 있습니다. 중첩된 incrementer() 함수는 두 개의 값을 캡쳐합니다. 바로 runningTotalamount 입니다. 이 값들을 캡쳐를 하고나면, incrementermakeIncrementer에 의해 반환됩니다. 이 함수는 클로져임 runningTotalamount 만큼 증가합니다.

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

이 예제의 반환 타입은 () -> Int 입니다. 즉, 값을 반환하는 것이 아닌, _함수_를 반환합니다. 이 함수는 어떠한 파라미터도 갖지 않습니다. 대신, 한 번씩 호출될 때 마다 Int 값을 반환합니다. 함수가 어떻게 다른 함수를 반환하는 지에 대해서는 Function Types as Return Types을 참고하세요.

makeIncrementer(forIncrement:) 함수는 정수 값 runningTotal 을 정의하였습니다. 이 값은 현재 incrementer의 현재 총합을 저장하고 반환됩니다. 이 변수의 초기값은 0 입니다.

함수 makeIncrementer(forIncrement:) 는 하나의 Int 파라미터로 forIncrement 라는 인자 레이블을 갖습니다. 그리고 amount 라는 파라미터 이름을 갖습니다. 이 인자 값은 runningTotal이 매번 호출 될 때, 얼만큼을 증가할 것인지를 정의합니다. 함수 makeIncrementer는 실제의 값의 증가를 수행할 중첩 함수인 incrementer를 정의합니다. 이 함수는 단순히 runningTotalamount 값을 더하고 이 총합을 반환합니다.

중첩함수를 따로 떼어 놓으면 이상하게 보일 것 입니다.

func incrementer() -> Int {
     runningTotal += amount
  return runningTotal
}

어떠한 파라미터도 incrementer() 함수는 갖고 있지 않습니다. 그리고, runningTotalamount 를 이 함수의 내부에서 참조하고 있습니다. 참조 값runningTotalamount를 주변 함수로부터 캡쳐하고 이것을 자신의 함수 내부에서 사용하고 있습니다. 참조 캡쳐링은 runningTotalamountmakeIncrementer가 종료된 이후에도 사라지도록 하지 않습니다. 또한, runningTotalincrementer 함수가 다음에 호출될 때에도 여전히 남아 있습니다.

최적화와 관련하여, 스위프트는 값의 복사본(a copy of a value)을 캡쳐하고 저장합니다. 단, 그 값이 클로져에 의해 변경되지 않아야 하며 그 값은 클로져가 생성된 이후에도 변경되지 않아야 합니다.

스위프트는 더 이상 사용하지 않는 변수를 처분하는 등의 모든 메모리 관리를 다룹니다.

여기에 makeIncrementer를 사용한 예제가 있습니다.

let incrementByTen = makeIncrementer(forIncrement: 10)

이 예제는 상수인 incrementByTen을 증가 함수를 참조하기 위해 설정하였습니다. 이 값은 runningTotal 변수를 호출할 때 마다 10씩 더합니다. 여러번 이 함수를 호출하면 다음과 같은 결과를 보입니다.

incrementByTen()
// 10을 반환
incrementByTen()
// 20을 반환
incremntByTen()
// 30을 반환    

만약 두번째 incrementer를 생성하면, 고유의 참조를 가지게 되어 새로운 runningTotal 변수가 설정됩니다.

let incrementBySeven = makeIncrementer(forIncrement:7)
incrementBySeven()
// return a value of 7

위에서 선언한 두 함수 incrementByTenincrementBySeven은 서로 영향을 받지 않습니다. 각자 개별적인 참조값을 가지게 됩니다.

참고

만약 여러분이 클로져를 어떤 클래스 인스턴스의 속성으로 할당하면, 클로져는 그 함수를 참조하여 인스턴스를 캡쳐합니다. 따라서, 여러분은 클로져와 인스턴스 사이에 강한 참조 싸이클을 생성하게 됩니다. 스위프트는 _리스트(list)_를 캡쳐하여 이러한 상한 참조 싸이클을 해제합니다. 더 자세한 정보는 Strong Reference Cycles for Closures 를 참고하세요.


원본 : https://docs.swift.org/swift-book/LanguageGuide/Closures.html