티스토리 뷰

Swift

값 캡쳐 (Capturing Values)

강경 2021. 3. 25. 19:16
728x90
반응형

이전에 클로저에 대해 어느정도 정리를 해뒀었지만, 통합하여 제대로 정리해보려 합니다..!
(아래 링크는 사실 무시하셔도 전혀 상관없습니다.. 블로그 초창기 공부하려고 시도했던 흑역사에요😭)
Closure - 1
Closure - 2
Closure - Capturing Values
Closure - 3



클로저는 상수나 변수의 값을 캡쳐(?)📸할 수 있어요
캡쳐라는 행위의 뜻은, 원본에 대한 사본을 만들어 둔다는 의미인데..🤯


지금 바로 이해하려고 하지말고..
처음에는 장점만 살펴보고, 예시를 보면 좋을거 같아요😅


클로져의 캡처를 이용하면 원본의 상수나 변수값이 사라지더라도,
사본(사진)🏞에 담겨있는 상수나 변수의 값을 사용할 수 있는거라고 생각하면 될거같아요!


캡쳐를 사용하는 예로, 중첩 함수(nested function: 함수의 body에서 다른 함수를 다시 호출하는 형태)를 들수 있어요!
그럼, 예시를 보도록 하겠습니다...!

중첩함수 안에 숨어있는 캡쳐📸된 녀석🏞을 찾아라!

func makeIncrementer(forIncrement amount: Int) -> 클로저 {
    var runningTotal = 0

    func 함수() -> Int {
        // ...
    }

    return 함수
}

바로 본모습을보면 헷갈릴 수도 있어서, 슈도코드느낌의 코드를 가져왔어요!
이렇게 함수 body에서 다른 함수를 호출하는 형태를 중첩함수라고 합니다!😲
현재 makeIncrementer() 함수 같은 경우에는
Int값을 인자로 받고, 이 함수 안에서 정의한 또 다른 함수를 반환하는 형태로 볼 수 있어요!
이제 이 함수의 원래 모습을 볼까요?

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0

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

    return incrementer
}

오호라~!
makeIncrementer()함수는
Int값인 amount를 인자로 가져와서,
함수의 body에서 어떠한 행위(?)를 진행하고,
incrementer()라는, Int값을 반환하는 클로저를 반환하는군요!



이제 함수의 body에서 일어나는 어떠한 행위(?)가 뭔지 봅시다🧐
먼저, incrementer()함수를 살펴보면

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

??? 함수내에서 정의하지 않은 runningTotalaount가 쓰이고 있군요..!
이 두녀석이 바로 캡쳐📸된 변수와 상수값입니다👍🏻

그래서, 왜 캡쳐📸를 하는것인가..?

let incrementByTen = makeIncrementer(forIncrement: 10)

자! 위에서 만든 makeIncrementer()함수의 반환 값을 상수incrementByTen에 담을 수 있겠죠?
그럼 incrementByTen이라는 상수는
makeIncrementer()함수의 body에 적혀있던 incrementer()함수의 역할을 수행하겠죠?
그럼 이 상수를 실행하면 어떤 일이 벌어질까요~?🤔

incrementByTen()
// 10을 반환

incrementByTen()
// 20을 반환

incrementByTen()
// 30을 반환

?😳??

느낌 상으로는 10만 3번 반환될거 같은데
계속 10씩 커지는게 어색하지않나요??
(어색하지 않으시다면, 단번에 이해되신겁니다.. 🎉)


다시 돌아가서, 우리가 두 녀석(runningTotal, aount)을 캡쳐했었죠??
aount는 처음 incrementByTen상수를 만들 때 Int값 10으로 고정되어 캡쳐되었어요.
그리고 runningTotal는 변수형태면서 runningTotal += amount의 행위를 하도록 캡쳐되었고요!
그래서 호출할 때 마다 runningTotal += amount이 행위를
다시말해, runningTotal += 10이 행위를 하게되는거에요!
그리고 계속 축적되는 runningTotal값을 반환하는 거고요!😮


(이 부분이 이해가 안되신다면, 위의 상황을 다시 한 번 읽거나 댓글을 달아주세요😁)

그럼, 뒤이어 새로운 클로저를 생성하면 캡쳐된 값이 달라질까요??

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

incrementByTen이라는 상수를 뒤이어, incrementBySeven라는 상수를 만들었어요.
이 상수는 이전 상수와는 다르게, amount에 7이 들어가는 형태에요!
그럼 캡쳐관계는 어떻게 되는 것일까요?

incrementByTen()
// 40을 반환

아무런 영향이 없습니다.
amount에 7이 들어가는 형태는
amount에 10이 들어가는 형태와
서로 다른 클로저이기 때문이죠!


심화 개념
그럼 좀 더 깊게 생각해서,
만약 어떤 클래스 인스턴스의 프로퍼티로 클로저를 할당하고
그 클로저가 그 인스턴스를 캡쳐링하게되면..?
강한 순환참조에 빠지게 됩니다..😫


즉, 인스턴스의 사용이 끝나도 메모리를 해제하지 못하는 것이죠.
그래서 Swift는 이 문제에 대해 캡쳐 리스트(capture list)라는 친구로 해결합니다🤩
더 자세히 알고자한다면, 클로저의 강한 참조 순환을 참조해주세요!🤔

캡처된 친구들은 각자의 값을 어떻게 공유하는 걸까요?😱

간단합니다, 함수와 클로저는 참조(reference) 타입이기 때문입니다.


함수와 클로저를 상수나 변수에 할당할 때에,
상수와 변수에 해당 함수나 클로저의 참조(reference)가 할당 됩니다.


그래서 만약,
한 클로저를 두 상수나 변수에 할당하면
그 두 상수나 변수는 같은 클로저를 참조하게 되는 것이죠..🤮

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 50 반환

그래서,
alsoIncrementByTen라는 새로운 상수에 incrementByTen상수와 연결되어있는 reference를 공유시키면
incrementByTen상수의 기존 값을 그대로 가져와서,
incrementByTen상수와 똑같은 역할을 하게 되는 것입니다!!



이렇게, 값 캡쳐 (Capturing Values)에 대해 알아보았어요!
클로저의 중요한 기능 중 하나라는건 알겠는데,
사실... 어떻게 활용하면 좋은지는 좀 더 고민해봐야 할거같아요🤔
(혹시나 알고계시고, 댓글 코멘트로 소개해주신다면 큰절올리도록 하겠습니다🙇🏻‍♂️)
다음 글에서는 클로저는 참조 타입 (Closures Are Reference Types)에 대해 소개해보겠습니다!


이해가 안되는 부분이나, 틀린 부분이 있으면 코멘트를 남겨주세요!
피드백은 정말정말 환영입니다🎉🎉

Reference

공식문서
공식문서 번역본

728x90
반응형

'Swift' 카테고리의 다른 글

LLDB(Low-Level Debugger)  (0) 2021.04.15
고차함수 (Higher Order Functions, 고계함수)  (0) 2021.04.12
후위 클로저 (Trailing Closures)  (0) 2021.03.25
클로저 표현 (Closure Expressions)  (0) 2021.03.23
이름 짓기  (0) 2021.02.13