Notice
Recent Posts
Recent Comments
Link
大器晩成
캡처리스트 본문
일전에 중첩함수에서 함수를 리턴하는 경우 클로저의 캡처 현상과 동일하다고 했습니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 100)
incrementByTen() // 100
incrementByTen() // 200
incrementByTen() // 300
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 400
- 위 예시를 보면 함수를 리턴하고 있으며, 변수에 함수를 담았기 때문에, 해당 변수는 heap 영역에 저장되며, runningTotal이 heap에 계속 머무르고 있기 때문에, 값이 0으로 초기화되지 않고 이전 값이 남아있는 현상이 캡처 현상입니다.
[캡처리스트의 형태]
// 1. 파라미터가 없는 경우
{ [캡처리스트] in
}
// 2. 파라미터가 있는 경우
{ [캡처리스트] (파라미터) -> 리턴형 in
}
[값 타입의 캡처]
var num = 1
let valueCapture = {
print("밸류 값 출력: \(num)")
}
num = 7
valueCapture() // 밸류 값 출력: 7
num = 1
valueCapture() // 밸류 값 출력: 1
- 밸류타입의 참조(메모리 주소)를 캡처합니다.
- 즉, 값 자체를 복사해서 가지고 있는 것이 아니고, num의 주소를 캡처해서 계속 사용하고 있습니다.
[값 타입의 캡처리스트]
var num = 1
let valueCaptureList = { [num] in
print("밸류 값 출력: \(num)")
}
num = 7
valueCaptureList() // 밸류 값 출력: 1
- 변수의 주소를 복사하는 것이 아닌 값 자체를 복사해서 힙 영역에 담고 있습니다.
- 그렇기 때문에 num의 값이 변경되어도 이전의 값을 가지고 있기 때문에, 클로저에서는 이전 값이 출력됩니다.
- 값 타입의 캡처는 waek, unowned를 사용할 필요가 없습니다.
*캡처현상: 클로저는 힙의 영역에 존재해야 하고, 클로저 내부에서 외부에 존재하는 변수를 계속 사용해야 하기 때문에 캡처 현상이 발생합니다.
[캡처리스트 사용이유]
- 값 타입은 값을 복사/캡처 (외부적인 요인에 의한 값 변경 방지)
- 참조타입은 캡처리스트 내에서 (메모리 주소를 캡처) weak / unwoned 참조 선언이 가능 (강함 참조 해결이 가능)
[참조 타입 캡처와 캡처 리스트]
class Bird {
var name = "none"
}
var sparrow = Bird()
var eagle = Bird()
print("참조 초기값:", sparrow.name, eagle.name)
let refTypeCapture = { [sparrow] in
print("참조 출력값(캡처, 캡처리스트):", sparrow.name, eagle.name)
}
sparrow.name = "참새"
eagle.name = "독수리"
refTypeCapture() // 참조 출력값(캡처, 캡처리스트): 참새 독수리
- eagle은 단순 캡처 현상이고, sparrow는 캡처리스트 현상이지만, 현재 동작은 동일합니다.
- 값 타입의 경우 캡처리스트와 캡처 현상이 다르게 발생했지만, 참조 타입은 차이가 없이 동일하게 동작합니다.
- 하지만 내부적으로 보면, 차이가 있습니다. sprrow는 캡처리스트 현상으로 sprrow의 주소를 복사해서 담고 있으며, 직접적으로 객체의 주소를 캡처하고 있습니다.
- eagle 스택영역에 있는 eagle의 주소(변수의 주소)를 가리키고 있습니다.
- 이렇게 인스턴스를 가리키는 경우 강한 참조 사이클이 발생할 수 있습니다.
[강한 참조 사이클 문제의 해결]
var sparrow = Bird()
let refTypeCapture = { [weak sparrow] in
print(sparrow?.name)
}
let refTypeCapture2 = { [unowned sparrow] in
print(sparrow.name)
}
- 약한 참조를 할 경우 가리키는 인스턴스가 사라질 경우 nil을 할당할 수 있어야 하기 때문에 옵셔널 타입으로 변경됩니다.
- weak / unowned를 사용하지 않으면 강한 참조입니다.
- 위의 예제에서는 클래스 내에서 클로저를 가리키고 있지 않아 강한 참조 사이클이 발생하지는 않습니다.
값 형식 | 참조 형식 | |
캡처현상 | 클로저는 힙의 영역에 존재해야 하고, 클로저 내부에서 외부에 존재하는 변수를 계속 사용해야 하기 때문에 캡처 현상이 발생합니다. |
|
캡처리스트 | 외부의 값을 복사해서, 내부에 저장하고 사용 | 외부의 참조 타입의 주소값을 복사해서, 내부에 저장하고 사용 |
weak, unowned참조 선언 가능 |
[참고]
- 캡처 리스트 내에서 바인딩하는 것도 가능합니다.
let refTypeCapture = { [weak test = sparrow] in
print("참조 출력값(캡처, 캡처리스트):", test?.name)
}
참고자료
[트러블슈팅] 중첩 클로저로 인한 순환 참조 및 메모리 누수 해결하기
안녕하세요! 개발콩입니다.오늘은중첩 클로저로 인한 순환 참조되어 메모리 누수가 발생되는 문제를 해결한 글에 대해서 작성하고자 합니다. 문제 상황output.updatePersonData .bind(to: tableView.rx.item
alstn38.tistory.com
728x90
'iOS > Swift 문법' 카테고리의 다른 글
제네릭(Generics) (0) | 2025.03.03 |
---|---|
에러 처리 (Error Handling) (0) | 2025.03.03 |
강함참조 사이클(Strong Reference Cycles Between Class Instances) (0) | 2025.03.03 |
스위프트 메모리 관리 모델 ARC(Automatic Reference Counting) (0) | 2025.03.03 |
고차함수 (0) | 2025.03.02 |