Skip to content

Latest commit

 

History

History
92 lines (82 loc) · 4.12 KB

10. Capture List.md

File metadata and controls

92 lines (82 loc) · 4.12 KB

날짜: 2023-01-29 18:43

주제: 외부 객체의 소유권을 제어하는 방법


메모:

What is Capture List

  • 캡처 목록은 클로저에서 참조하는 외부 객체의 소유권을 제어하는 기능이다.
  • 주변 환경의 범위에서 참조한 변수들을 얼마나 강하게 캡쳐해야하는지를 명시하는 것으로, 캡쳐리스트를 사용하여 메모리 누수를 일으키는 강한 참조 순환을 피할 수 있게 된다.
  • 캡쳐리스트는 참조 방식과 참조할 대상을 대괄호([])로 둘러싼 목록 형식으로 작성하고, 캡쳐리스트 뒤에는 in 키워드를 써준다.  **캡쳐리스트에 명시한 요소가 참조타입이 아니라면 해당 요소들은 클로저가 생성될때 초기화된다.
  • 참고로 얼마나 강하게 캡쳐할지 지정하는 옵션 키워드(weak, unowned)는 참조타입을 캡쳐할 때만 사용 가능하다.

When do we use it

  1. 클로저의 강한참조 순환 문제를 해결하기 위해 사용 (참조 타입)
  2. 클로저 내부에서 클로저 외부의 값을 참조할 때 참조하는 값이 변경되면 클로저 내부에서도 참조하는 값 또한 바뀌게 되므로 이를 방지하고자 사용 (값 타입)

How to use it

클로저의 강한 참조 순환 문제
  • 강한 참조 순환이 발생하는 이유는 클로저가 클래스와 같은 참조타입이기 때문이다. 클로저를 클래스 인스턴스의 프로퍼티로 할당하면 클로저의 참조가 할당된다. 이때 참조타입과 참조타입이 서로 강한 참조를 하기 때문에 강한참조 순환 문제가 발생한다. 이때 캡쳐리스트에 옵션을 지정해주면, 참조 횟수(Reference Counting)을 증가할지 여부를 결정할 수 있다.
class ViewController {
  var closure: () -> Void = {
    print(self.description)
  }
  deinit {
    print("ViewController deinitialized")
  }
}
  • 위 코드에서 self는 ViewController의 객처 참조를 갖는다. 하지만 클로저를 호출할 때, ViewController 객처가 메모리에서 해제되어도 클로저는 여전히 존재하므로, 메모리 누수가 발생할 수 있다.
class ViewController {
  var closure: () -> Void = { [weak self] in
    guard let self = self else { return }
    print(self.description)
  }
  deinit {
    print("ViewController deinitialized")
  }
}
  • 위 코드에서 클로저 정의 앞에 [weak self]를 사용하여 self를 weak 참조로 제어하게 된다. 이는 클로저에서 self를 참조하는 경우, ViewController 객처가 메모리에서 해제되고 나서도 클로저 또한 해제 되므로, 메모리 누수가 발생할 가능성이 없다.
클로저 내부에서 외부의 값 참조
var index = 0
var closureArr: [() -> ()] = []

for _ in 1...5 {
  closureArr.append({print(index)})
  index += 1
}

for i in 0..<closureArr.count {
    closureArr[i]()
}

// 5 5 5 5 5 
  • closureArr.append() 시 변수 index 가 반복문을 돌면서 최종값으로 5가 되는데, 클로저가 이미 변경된 index 값인 5를 참조하기 때문이다.
var index = 0
var closureArr: [() -> ()] = []

for _ in 1...5 {
  closureArr.append({[index] in 
   		print(index)       
   })
  index += 1
}

for i in 0..<closureArr.count {
    closureArr[i]()
}

// 0 1 2 3 4 
  • 이전 코드와 다른 점은, 클로저 안에 참조하는 변수에 대괄호[]를 붙여줌으로 캡쳐 리스트를 사용한다고 명시했다.
  • 캡쳐 리스트시 값타입의 캡처 시점은 클로저가 생성될 때 이기 때문이다.
  • 요약하자면 캡쳐리스트에서 캡쳐 시점은 값타입 / 참조타입에 따라 나뉘게 된다.
    • 값타입 => 클로져가 생성될 때 캡쳐
    • 참조타입 => 클로져가 호출될 때 캡쳐

출처(참고문헌)

연결문서

  • [[8. ARC (3)]]
  • [[11. Closures]]

Tag

  • #Swift/Closures
  • #Swift/Capture_List