[iOS] 값 캡처(Capturing Values)

mmim·2022년 8월 1일
0

값 캡처란?

우선 공식 문서에서는 클로저 부분에서 값 캡처를 언급한다.
클로저는 정의된 주변 context에서 상수/변수를 캡처할 수 있다고 말한다.
이렇게 캡처가 되면 상수/변수를 정의한 scope가 존재하지 않아도 그 값을 참조하고 수정할 수 있다.
🤔 오호 ... 어렵다..뭔 말인지...

아주 쉬운 예시를 들어보자.

func some() {
    var aaa = "A"
    var bbb = "B"
    let closure = {
        print(bbb)
    }
}

이런 함수가 있다고 가정하면
클로저는 외부 변수 bbb를 내부에서 사용한다.
이때, 클로저는 외부의 값을 내부적으로 저장하는데 !!! -> 아주 간단하게 이것을 값 캡처라고 부른다.
(aaa는 클로저 내부에서 사용되지 않았으니 당연히 캡처되지 않는다!!!)

이번에는 좀더 이해를 돕기 위해 공식문서에서 제공하는 예시를 살펴보자.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
  1. makeIncrementer 함수는 함수 자체를 리턴한다.
  2. makeIncrementer 안에는 incrementer 함수가 존재한다.(중첩 함수)
  3. makeIncrementer 안에는 runningTotal를 변수로 정의했다.
  4. incrementer은 내부에서 연산을 통해 runningTotal을 리턴한다.
  5. 마지막으로 makeIncrementer는 incrementer(함수 자체)를 리턴한다.

자! 이제 결과를 확인해보자.

let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementBySeven = makeIncrementer(forIncrement: 7)
  1. incrementByTen, incrementBySeven 상수를 각각 정의해준다.
  2. 다음 incrementByTen, incrementBySeven을 여러번 호출하면!!!
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30

incrementBySeven() // 7
incrementBySeven() // 14
incrementBySeven() // 21
  1. 연속으로 각기 호출했지만 누적된 결과가 나온다... 그 이유는

    runningTotal과 amount가 캡쳐되서 그 변수를 공유하기 때문!!!

  2. incrementByTen(), incrementBySeven()는 각기 다른 클로저이기 때문에 다른 캡쳐 저장소를 사용한다. 따라서 서로의 연산에 전혀 영향을 주지 않는다.

클로저는 참조 타입

아니 그런데... 앞선 예제를 살펴보면 let incrementByTen
분명 상수(let)으로 할당했는데 값이 증가하는(변하는) 것을 볼 수 있다.
어떻게 이게 가능할까?🤔 답은 함수와 클로저는 참조 타입이기 때문에 가능하다!

함수와 클로저를 상수나 변수에 할당할 때 실제로는 상수와 변수에 해당 함수나 클로저의 reference가 할당된다. (reference를 할당할 뿐 진짜 값을 할당한게 아니다.)

만약에 한 클로저를 두 상/변수에 할당하면 두 상/변수를 같은 클로저를 참조하고있다.(같은 referance를 갖게된다.)

클로저와 메모리 영역

"클로저는 참조 타입"이라는 말이 크게 와닿지 않는다. 메모리 영역에선 어떻게 움직이는지 전혀 감이 잡히지 않는다. 😭
이해를 최대한 해보고자
이번에는 위 예시들이 메모리에선 어떻게 움직이는 지 도식화해보자.

1️⃣ makeIncrementer가 호출되면, incrementByTen에는 heap의 주소값이 stack에 저장된다.
2️⃣ 그리고 heap에는 값을 저장할 공간이 할당된다.

let incrementByTen = makeIncrementer(forIncrement: 10)

3️⃣ 클로저를 호출하는 과정이다. 이때, 비로소 선언만 되었던 클로저가 검증단계에 들어선다. makeIncrementer 내부의 runningTotal 초기값이 클로저 내부로 들어가면서 heap영역에 할당된다. (runningTotal = 0)

incrementByTen()

4️⃣ 클로저 내부의 연산을 통해 runningTotal이 증가하고 heap영역의 runningTotal 값이 바뀐다.

runningTotal += amount
return runningTotal   

5️⃣ incrementBySeven 상수를 선언하면 다른 클로저가 할당되고, 이전 주소값과 다른 주소값이 stack에 저장된다.
6️⃣ 이전 과정과 같지만 다른 주소에 값을 저장한다.

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


공식문서
Swift) 클로저(Closure) 정복하기(3/3) - 클로저와 ARC

profile
예비 iOS 개발자의 기록

0개의 댓글