Swift, Memory and Managin’

leeokmin
nbt-tech
Published in
5 min readMay 22, 2020

안녕하세요. G파티에서 캐시슬라이드 스텝업 iOS(이하 스텝업)를 개발하고 있는 이옥민 입니다. 오늘은 스텝업의 유지보수를 하면서 중점적으로 보고 있는 메모리 관리에 대한 이야기를 해봅시다.

현대의 고차원 개발언어는 다양한 메모리 관리를 다룹니다. Swift 또한 마찬가지인데요. Swift는 메모리 관리를 위해서 Auto Reference Counting(aka ARC) 기능을 사용합니다.

ARC는 자동으로 동작합니다. 참조 횟수를 신경 쓸 필요는 없습니다. 그러나 객체 간의 관계를 파악해야 메모리 관리를 할 수 있습니다. 놓치기 쉬운 부분이죠.

시작하기

직접 빌드와 실행을 할 수 있도록 XCode의 Playground를 이용합니다. Playground에서 다음 코드를 추가합니다.

먼저 User 클래스를 정의합니다. 그 다음 run() 메소드를 만들어 user 인스턴스를 생성합니다. 마지막으로 run() 메소드를 호출합니다.

코드를 실행하면 콘솔에 다음과 같이 출력됩니다.

User Alice was initialized 
Deallocating user named: Alice

user의 초기화와 할당해제가 각각 완료된 것을 알 수 있습니다. run() 메소드의 시작에서 user가 초기화 되면서 레퍼런스 카운트(Reference Count)가 증가해 1이 됩니다. 그리고 메소드의 끝에서 레퍼런스 카운트가 감소해 0이 되고 참조되는 수가 0이므로 메모리에서 할당해제 되는 것입니다.

참조 사이클 문이 열리면

대부분의 경우는 위와 같이 ARC가 잘 작동합니다. 사용하지 않는 객체는 자동으로 할당해제 됩니다.

슬프지만 메모리 누수는 일어납니다. 메모리 누수가 어떻게 발생하는지 알아봅시다. Playground에 이어서 다음 코드를 추가합니다.

Ad 클래스를 정의합니다. 잠시 살펴봅니다. name과 receiver라는 프로퍼티가 있습니다. receiver는 User 타입이고 옵셔널 입니다.

run 메소드는 아래와 같이 수정합니다.

코드를 실행하면 콘솔에 다음과 같이 출력됩니다. ARC가 잘 작동해 메모리가 할당되었다가 해제됩니다.

User Alice was initialized.
Ad 10% off was initialized
Dellocating ad named: 10% off
Deallocating user named: Alice

대망의 순간입니다. User 클래스를 다음과 같이 수정합니다.

ads 배열이 추가되었습니다. set을 private로 해두었기 때문에 같이 추가한 add() 메소드를 이용해야만 ads 배열을 수정할 수 있습니다. 이 메소드가 ad가 추가될 때 ads에 적절하게 추가되는 것을 보증합니다.

run 메소드를 아래와 같이 수정합니다.

user의 add메소드를 이용해 ad를 추가하는 코드가 추가 되었습니다. 코드를 실행하면..어떤가요? user와 ad가 메모리에 할당되지만 해제되지는 않습니다.

User Alice was initialized.
Ad 10% off was initialized

대체 무슨 일이 벌어지고 있는 걸까?

User와 Ad 클래스의 참조관계. 숫자는 레퍼런스 카운트 값입니다.

user와 ad는 run() 메소드의 시작에서 레퍼런스 카운트가 1증가하고 메소드의 끝에서 1 감소합니다. 그 사이에 있는 add() 메소드 때문에 카운트가 각각 1 증가 했습니다. 그래서 두 객체 사이에 위의 그림과 같은 참조 사이클이 생겨서 할당해제가 되지 않는 상황입니다. 이 사이클을 끊어내야 ARC가 의도대로 동작할 수 있습니다. 여기서 weak 라는 키워드가 등장합니다.

Swift에서 기본적인 참조 타입은 strong 입니다. strong 타입 객체는 레퍼런스 카운트를 증가시키죠. 반면에 weak 타입은 레퍼런스 카운트를 증가시키지 않습니다. 아래의 그림처럼 말이죠.

레퍼런스 카운트가 0이되어 user는 할당해제 될 수 있습니다.

Ad클래스의provider 클래스를 다음과 같이 수정합니다

weak var receiver: User?

receiver의 참조 타입을 weak로 변경함으로써 참조 사이클을 끊어냅니다. 다시 빌드하고 실행해보세요. 정상적으로 할당 및 해제가 되는 것을 확인할 수 있습니다.

결론

참조 사이클이 생기지 않는다면 ARC는 잘 작동합니다. 참조 사이클이 생긴다면 적절하게 끊어 ARC가 동작하도록 하는 것이 개발자의 역할 중 하나입니다. 메모리 관리를 신경 쓰면서 더 안정적인 스텝업이 되도록 노력하겠습니다.

사실 weak 타입이외에 unowned 타입도 존재합니다. 전부 다르기에는 분량이길어서 여기서는 급하지 않았습니다. 다음에는 unowned를 포함해 strong, weak와 비교하며 이야기를 이어볼 예정입니다.

참조

https://www.raywenderlich.com/966538-arc-and-memory-management-in-swift

--

--