본문 바로가기

iOS/JourneyOfFaith App

[iOS] No 3. 메모리 점유율 줄이기. Retain cycle 형성 원인 분석 및 해결 과정

 

안녕하세요! 

 

오늘은 앱 UI 리펙터링을 하기 전에 화면을 A -> B -> A -> B 두 화면을 왔다 갔다를 반복하면서 마주한 메모리 점유율이 계단식으로 상승했던 이유와 어떻게 파악했는지 원인과 해결 과정을 소개하려고 합니다.

# Memgraph

# instruments - Allocations

# retain cycle

# lottie

# functional programming

 

화면 소개 및 메모리 계단식 점유 발생 이슈

문제가 곧 발생될? 기본 성경 읽기 플랜 탭 화면

 

성경 읽기 플랜 화면은 다음과 같이 구성되어 있습니다! 처음에 들어갈 때는 메모리 관련 이슈가 없지만, 3초 간격을 두며 반복해서 형광펜, 성경 읽기 플랜 두 화면을 전환하다 보면 메모리가 계단으로 점유되는 현상을 발견했습니다.

 

(화면 반복 왔다 갔다)

 

ViewController는 deinit될 수 있지만, 그럼에도 내부 컴포넌트에 의해 retain cycle이 발생할 수 있습니다!

 

 

20번의 화면 전환으로 메모리 점유율은 상승 추세를 형성한 것을 알 수 있습니다. 이는 retain cycle!을 의심할 수 있습니다. 

 

1. 클로저에 강한참조?

2. 델리게이트 채택할때 강한 참조? 등 여러 경우가 있습니다.

 

따라서 이번에는

1. 메모리 점유율이 왜 이렇게 발생됬는지

2. 메모리를 주로 많이 잡아먹는 요인이 무엇인지 파악을 해보려 합니다.

 

1. Memgraph 사용하기

 

런타임에서 메모리 점유율이 432MB 최고치를 달성중이면서 점유중일 때 memgraph를 켭니다. 주요 요인이 되는 reusable view인 BibleChallengeView를 클릭해서, bytes가 높은 3개 의 ref 만 참조합니다. 

 

 

어 그리고 

 

그리고 이렇게 왼쪽으로 reference를 추려나가다 보면 4 3 2 1 이렇게 결국 4에서 참조하는 객체가 다시 1에서 자기자신이 강하게 참조 되는데 이때 진한선(string reference)입니다. (4 == 1 화살표가 끊기지 않고 무한정 반복될 수 있음)

 

원인은 BibleChallengeCheckCell에서 클로저(2)를 쓰는데 그 프로퍼티가 BibleChallengeView(1)을 강하게 참조했기 때문임을 알 수 있습니다.

 

 

 

예전에 개발해서 기억이 안난다면? 클로저 정확히 뭔지 봐보면 봐보면

 

옹.. checkBoxTap이라는 객체라네요.

 

 

실제로 BibleChallengeCheckCell을 보면

 

어 정말 그대로 있네요. 

 

그리고 이 클로저는

 

 

여기! 이 코드는 BibleChallengeView(1) 코드 안에 117라인 입니다. handleCheckBox(_:state:)이 또한 BibleChallengeView(1)의 함수입니다. 여기서 전 functional programming을 특징인 일급객체 개념을 활용해 함수를 주입했는데, 이때 강한 참조가 형성됬다는 것을 알 수 있습니다!

 

막 매번 이런 강한 참조가 발생되는건 또 아닌데

이게 클로저 매개변수가 클로저를 선언한 클래스 자기자신 Self를 참조하는 class타입일 경우 외부에서 함수 type을 주입할 때 강한 참조가 생길 가능성이 높습니다.  ( UIColelctionViewCell) 여기에 자기자신이 들어가는데, 이런 경우에 그렇습니다.

 

 

 

그럼 다시 늘 사용하던 weak 하게 클로저 주입시 self?.함수를 호출하면 됩니다.

 

 

그럼 이렇게 됩니다. 메모리는 다행히 특정한 화면에서 요구하는 점유율만큼 메모리만큼 점유하게 됩니다.

 

 

Retain cycle은 VC, VM, reactor 뿐 아니라 내부 컴포넌트에 의해서 발생될 수 도 있구.. 일급객체를 사용할때 조심해서 사용해야 합니당.

내부에서 사용하는건 좋은데 객체간 이렇게 주입할때는 조심!!

 

2. Instruments 활용하기

 

아까 언급했던 맨 처음 상승 추세를 연이어 달성하던 메모리 점유율 상황에서 Profiling을 해봤습니다. 메모리 릭 체크가 안나오네요. . . 뷰 컨이 deinit이 성공적으로 호출되서 그런가,, 그렇지만 메모리 크기가 350MB 이상을 사용하는 것을 알 수 있습니다.

 

여기서는 뭐가 이렇게 메모리를 많이 잡아먹었나 살펴보려 합니다.

 

1. 로티사진

 

아,,,, lottie네요. 믿고있었는데ㅠㅅㅠ

 


로티는 위에 영상에서 보면 달력?같이 생긴 셀 컴포넌트에서 사용되는데, BibleChallengeDayCell 클래스 안에 저렇게 정직하게 선언되어 있습니다. 이 객체를 외부, 클로저, 어디 외부로 전달하지 않습니다. 사용자에 의한 특정 이벤트가 발생되면 그때 단 한번 play()를 통해 실행됩니다.

 

그럼에도 lottie에 의해 메모리 점유가 지속될 수 있습니다. (저렇게 1. 로티사진 처럼 cycle을 형상하며,,) 이번에 다시 한 번 로티 자체가 메모리 점유를 많이 잡아먹는다는 것을 알게됬습니다.

 

 

 

그래서 이렇게 특정 이벤트 발생시 호출하고 제거하는 방식을 사용해서 메모리를 좀 더 줄였습니다.