Swift Memory Part.2

만사·2021년 2월 12일
0

메모리

목록 보기
3/4
post-thumbnail

part1에서는 메모리 누수가 일어나는 경우 중 참조로 인한 메모리 할당해제가 이루어지지 않아 생기는 메모리 누수를 찾는 방법과 수정하는 방법 중 weak 과 unowned를 사용했다. Part2는 swift에서 절대 빼놓을 수 없는 Closure를 사용할 때 생기는 메모리 누수 문제와 그것을 어떻게 수정하는지를 공부해본다.

클로저를 사용할 때 생기는 메모리 누수가 생긴다는 것은 클로저는 참조 유형(Reference Type)이라는 것이다.!! 특별히 클로저를 사용할 때 Retain Cycle이 잘 발생한다.

Closure는 Reference Type이다!!

간단한 예제로 알아본다.

ViewController에는 시간을 나타내는 dateLabel과 DestinationVC로 segue로 연결된 버튼이 있다. prepare메서드를 보면 DestinationVC의 onSave 클로저를 다루고 있다.

DestinationVC는 datePicker와 showTimePicker변수/ onSave 클로저 변수가 있다. 현재는 클로저가 레퍼런스 타입이라는 것을 확인하기 위함이기 때문에 코드 동작은 생략한다. DestinationVC의 viewDidLoad에서 호출되는 20라인에 브레이크 포인트를 걸고 앱을 실행 후 DestinationVC로 push를 하면 20라인에서 브레이크된다.

onSave클로저가 메모리 주소에 올라간 것을 볼 수 있다.

Closure를 사용할 때 발생하는 Retain Cycle

위 예제에서 어떤 retain cycle이 생기는지 보자.

  1. DestinationViewController -> onSave closure(property on DestinationViewController)
  • 클로저는 참조유형이기 때문에 DestinationVC클래스에서 onSave 클로저를 선언하면 일단 DestinationVC라는 부모 클래스는 onSave자식 참조유형을 참조하게 된다. 즉 DestinationVC는 onSave에 대한 의존성이 생겼다. var로 선언했기 때문에 Strong Reference로 참조된다.
  1. onSave closure(property on DestinationViewController) -> ViewController(self)
popup.onSave = { data in
    self.dateLabel.text = data // self를 선언하므로 의존!
}

ViewController에서 onSave 클로저를 사용할 때 클로저는 self 즉 ViewController를 참조, 의존한다. 여기서 Strong Reference로 참조된다.

  1. ViewController(self) -> dateLabel(Label on ViewController)
@IBOutlet weak var dateLabel: UILabel!

popup.onSave = { data in
    self.dateLabel.text = data 
}

ViewController(self)는 dateLabel을 참조하였다. dateLabel은 weak로 선언되었기 때문에 weak reference로 참조된다.

예를 들어, ViewController에서 DetinationVC의 상태나 행동을 알아야해서 인스턴스를 하나 생성했다.

이런 상태가 된다면 ViewController가 DestibationVC를 참조하게 된다.

결과적으로,

이러한 retain cycle이 생긴다.

클로저 안의 self..?

조금 어려운건 self이다. onSave클로저 안에 self를 선언하니 참조가 생기고 self.dateLabel을 하니 참조가 생겼다. 이건 어떤 의미일까? 그 이유는 closure는 capturing value이기 때문이다.

  • Capturing Values
    : 중첩 함수는 외부 함수의 인수를 캡처 할 수 있으며 외부 함수 내에 정의 된 모든 상수 및 변수를 캡처 할 수도 있습니다. (번역,,)

예를 들어,

@IBOutlet weak var myLabel: UILabel!

func doAnimation(showLabel: Bool) {
    let text = "Some Text"
    
    UIView.animate(withDuration: 0.3) {
        if showLabel {
            self.myLabel.alpha = 1
        } else {
            self.myLabel.alpha = 0
        }
        self.myLabel.text = text
    }
}

UIView.animate 메서드는 클로저다. 여기서

if showLabel {
    self.myLabel.alpha = 1
 } else {
    self.myLabel.alpha = 0
    }
self.myLabel.text = text

이 부분이 중첩함수이다. 중첩함수는 함수 안에 함수를 뜻한다.

외부함수는 doAnimation함수를 뜻한다. 위 예제로 capturing values를 다시 정의하면

중첩 함수 (animate의 클로저)는 외부 함수(doAnimation)의 인수(showLabel)를 캡처 할 수 있으며 외부 함수(doAanimation) 내에 정의 된 모든 상수 및 변수(text)를 캡처 할 수도 있습니다.

다시 코드를 보자!

@IBOutlet weak var myLabel: UILabel!

func doAnimation(showLabel: Bool) {
    let text = "Some Text"
    
    UIView.animate(withDuration: 0.3) {
        if showLabel {
            self.myLabel.alpha = 1
        } else {
            self.myLabel.alpha = 0
        }
        self.myLabel.text = text
    }
}

Capturing Values는 argument, let, var를 캡처할 수 있기 때문에 showLabel과 text는 마음대로 사용했지만, myLabel은 외부함수에서 정의되지 않았기 때문에 self를 참조, 의존해야만이 myLabel을 캡처할 수 있다.

그리고 capturing value는 값을 캡처할 때 기본적으로 Strong Reference로 수행된다.

Capture List

앞의 문제를 Capture List(획득 목록)으로 해결할 수 있다. 획득목록은 클로저 중첩함수 내에서 참조를 획득하는 규칙을 제시한다. 예를 들어, 위 예의 showLabel, text를 약한참조로 지정할 수도있다.

획득 목록을 설정하는 방법은,

@IBOutlet weak var myLabel: UILabel!

func doAnimation(showLabel: Bool) {
    let text = "Some Text"
    
    UIView.animate(withDuration: 0.3) { [label = self.myLabel!] in
    
        if showLabel {
            label.alpha = 1
        } else {
            label.alpha = 0
        }
        label.text = text
    }
}

획득 목록의 문법은

[label = self.myLabel!] in

[weak label = self.myLabel!] in

[unowned vc = self] in

[unowned self] in

이렇게 사용이 가능하다!

처음 예제에 capture list를 적용하여 retain cycle을 제거하면,

[unowned vc = self]를 통해 획득 목록을 적용했다.

결과적으로 onSave 클로저가 미소유 참조로 VC를 참조하기 때문에 Retain Cycle을 제거할 수 있다.

이것만은 기억하자!

획득 목록을 사용하여 클로저에 들어가는 변수의 강도를 변경할 수 있습니다.

UIView.animate(withDuration: 0.3) { [weak self] in
    self?.label.alpha = 1
}

0개의 댓글