[iOS_Swift] deinit이 호출되지 않는 이유

2022. 1. 19. 23:48Programming/iOS_Swift

  프로젝트를 만들 때, 간혹 뷰컨트롤러를 분명히 Pop 했음에도 해당 뷰컨트롤러의 소멸자가 호출이 되지 않는 경우가 존재할 수 있다. 이럴 경우에는 해당 뷰컨트롤러를 더 이상 사용하지 않음에도 불구하고 메모리에서 계속 해당 뷰를 붙잡아두고 있는 것을 의미한다. 이는 메모리 누수(Memory Leak)에 해당하며, 앱의 속도를 떨어뜨릴 수 있고 심해진다면 Out of Memory 크래시를 일으킬 수 있다.

 

  deinit이 호출되지 않는 이유는 다음과 같은 몇 가지 케이스들이 있다.

 

1. 프로토콜 타입의 변수가 강한 참조를 하고 있을 때

  위와 같은 코드의 경우,  클래스 내에 프로토콜 타입의 변수 하나를 선언해줬다. 해당 변수는 현재 강한 참조를 하고 있기 때문에, SecondViewController를 강하게 붙잡고 있다. 이럴 경우에 SecondViewController는 더 이상 사용하지 않는 상황이 오더라도 강한 참조를 하고 있는 delegate변수 때문에 deinit이 호출 되지 않는다.

  반드시 프로토콜 타입의 변수를 만들 때에는 약한 참조를 하도록 하자!

정상적으로 deinit이 호출된다.

2. 클로저 내에 "weak self"를 사용하지 않았을 때

  클로저를 사용하다보면 우리는 자주 외부 Context에 있는 값들을 캡쳐해서 사용한다. 이 때, weak self를 사용하지 않는다면 외부 Context를 강한 참조로 캡처하게 되고 Retain Count가 증가하게 된다. 이렇게 증가한 Retain Count는 따로 처리해주지 않는 이상 계속해서 메모리에 남아 메모리 누수를 일으키게 된다. 간단한 예시를 통해 이해해보자.

 프로젝트에서 사용했던 함수 하나를 가져왔다. 위의 코드를 보면 클로저를 사용하고 있는데, 외부 Context인 coordinator 변수를 사용하기 위해 self를 약한 참조로 캡처하고 있다. 이렇게 약한 참조를 했을 경우에는 Retain Count가 증가하지 않지만, weak self를 쓰지 않고 강한 참조로 self를 캡처할 경우 Retain Count가 증가하고 계속해서 메모리에 남게 된다. 클로저를 쓸 때는 꼭꼭 습관적으로 weak self를 쓰도록 하자.

 

왜 알아야 할까?

  만약 이 개념을 모른 상태로 개발을 하게 된다면, 여러 분도 모르는 사이에 프로젝트 곳곳에서 메모리가 낭비되고 있을 확률이 매우 높다! 테스트를 해보고 싶다면 프로젝트 내의 모든 뷰컨트롤러나 클래스에 deinit이 제대로 호출되고 있는지 확인해보자. 부끄럽지만 나도 이 개념을 모르고 개발하다가 팀원이 지적해줘서 알게 된 이슈였다. 해당 문제를 개선하니까 앱이 정말 눈에 띌 정도로 빨라졌다. 이렇듯 앱의 속도와 메모리가 관련된 중요한 개념이므로 꼭 알아두는게 좋을 것 같다.

 

** 왜 알아야 할까? 부분은 현재까지 제가 느낀 사견이기 때문에, 틀릴 수도 있습니다. 내용이 잘못됐다면 언제든 지적해주세요!