🔥 클로저와 값 캡처

509자
7분

Swift에서 클로저는 정의된 주변 컨텍스트에서 상수와 변수를 캡처(capture)할 수 있습니다. 클로저는 원래 상수와 변수를 정의한 스코프가 더 이상 존재하지 않더라도 캡처한 상수와 변수를 참조하고 수정할 수 있죠.

Swift에서 값을 캡처할 수 있는 가장 간단한 형태의 클로저는 바로 중첩 함수(nested function)입니다. 중첩 함수는 다른 함수의 내부에 작성되며, 외부 함수의 인자와 외부 함수 내에 정의된 상수 및 변수를 캡처할 수 있어요.

아래는 makeIncrementer라는 함수의 예시인데요, 이 함수는 incrementer라는 중첩 함수를 포함하고 있습니다. 중첩된 incrementer() 함수는 주변 컨텍스트에서 runningTotalamount라는 두 개의 값을 캡처합니다. 이 값들을 캡처한 후, incrementermakeIncrementer에 의해 클로저로 반환되고, 호출될 때마다 runningTotalamount만큼 증가시키죠.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0 // 현재까지 증가된 값을 저장할 변수
    func incrementer() -> Int {
        runningTotal += amount // amount만큼 runningTotal을 증가시킴
        return runningTotal // 증가된 runningTotal 값을 반환
    }
    return incrementer // incrementer 함수 자체를 반환
}
swift

makeIncrementer의 반환 타입은 () -> Int입니다. 이는 단순한 값이 아닌 함수를 반환한다는 의미이죠. 반환되는 함수는 매개변수가 없으며, 호출될 때마다 Int 값을 반환합니다. 함수가 다른 함수를 반환하는 방법에 대해 더 알고 싶다면 Function Types as Return Types를 참고해 보세요.

makeIncrementer(forIncrement:) 함수는 반환될 incrementer의 현재 누적 값을 저장할 runningTotal이라는 정수 변수를 정의합니다. 이 변수는 0으로 초기화되어 있어요.

makeIncrementer(forIncrement:) 함수는 forIncrement라는 인자 레이블과 amount라는 매개변수 이름을 가진 단일 Int 매개변수를 가집니다. 이 매개변수에 전달된 인자 값은 반환된 incrementer 함수가 호출될 때마다 runningTotal을 얼마나 증가시킬지 지정하죠. makeIncrementer 함수는 실제 증가 작업을 수행하는 incrementer라는 중첩 함수를 정의합니다. 이 함수는 간단히 amountrunningTotal에 더하고 결과를 반환해요.

고립된 상태에서 본다면 중첩된 incrementer() 함수는 좀 특이해 보일 수 있습니다:

func incrementer() -> Int {
    runningTotal += amount // 외부 함수의 변수들을 캡처해서 사용
    return runningTotal
}
swift

incrementer() 함수는 매개변수가 없지만, 함수 본문 내에서 runningTotalamount를 참조하고 있어요. 이는 주변 함수로부터 runningTotalamount에 대한 참조를 캡처하고 자체 함수 본문 내에서 사용하기 때문에 가능한 일이죠. 참조로 캡처하면 makeIncrementer 호출이 끝나더라도 runningTotalamount가 사라지지 않고, 다음에 incrementer 함수가 호출될 때 runningTotal을 사용할 수 있게 해줍니다.

아래는 makeIncrementer가 실제로 작동하는 예시에요:

let incrementByTen = makeIncrementer(forIncrement: 10)
swift

이 예시는 incrementByTen이라는 상수를 정의하여 호출될 때마다 runningTotal 변수에 10을 더하는 incrementer 함수를 참조하도록 합니다. 이 함수를 여러 번 호출하면 동작 방식을 확인할 수 있어요:

incrementByTen() // 10 반환
incrementByTen() // 20 반환
incrementByTen() // 30 반환
swift

두 번째 incrementer를 만들면, 새롭고 별도의 runningTotal 변수에 대한 자체 저장된 참조를 갖게 됩니다:

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() // 7 반환
swift

원래의 incrementer(incrementByTen)를 다시 호출하면 incrementBySeven에 의해 캡처된 변수에 영향을 주지 않고 자체 runningTotal 변수를 계속 증가시킵니다:

incrementByTen() // 40 반환
swift

이렇게 Swift의 클로저는 주변 컨텍스트에서 상수와 변수를 캡처할 수 있습니다. 이를 통해 클로저 내부에서 외부 스코프의 값들을 유연하게 활용할 수 있죠. 캡처된 값들은 클로저가 정의된 원래 스코프가 사라져도 클로저 내에서 계속 유지되고 참조될 수 있습니다.

또한 캡처는 값 타입과 참조 타입에 따라 동작 방식이 달라져요. 값 타입(value type)은 값 자체가 캡처되어 복사되는 반면, 참조 타입(reference type)은 참조가 캡처되어 원본 인스턴스를 공유하게 됩니다.

캡처의 개념을 잘 이해하면 클로저를 매우 강력하고 표현력 있게 사용할 수 있으니, 꼭 숙지해 두시는 게 좋겠죠? 클로저와 캡처를 적절히 활용하면 코드의 가독성과 재사용성을 높일 수 있답니다!