[Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라
출처 : Effective Objective-C
-
메모리 관리 오류를 디버깅하는 것은 매우 어려운 일이다.
할당 해제된 객체에 메시지를 보내는 일은 예상할 수 있듯이 전혀 안전하지 못하다.
그러나 때때로 그것은 동작하기도 하고 동작하지 않기도 하는데 이는 객체가 사용했던 메모리를 덮어 썼느냐에 달려있다.
메모리가 재사용되었는지 알 수 있는 방법은 없다.
그로 인해 크래시가 가끔 일어난다.
메모리의 일부분만 재사용되는 경우도 있는데 이 때 객체의 일부분은 여전히 유효하다.
또한 순전히 운으로 또 다른 유효한 살아 있는 객체가 메모리를 덮어쓸 수도 있다.
이 경우 런타임은 새로운 객체에 메시지를 보낼 것이고 객체는 응답을 할 수도, 하지 않을 수도 있다.
이 경우 크래시는 발생하지 않겠지만, 왜 메시지를 보낸 객체가 여러분의 의도대도 동작하지 않는지 알 수 없을 것이다.
객체가 선택자에 응답하지 않으면 앱은 보통 크래시된다.
-
운 좋게도 코코아에는 손쉽게 사용할 수 있는 ‘좀비(zombie)’라는 기능이 있다.
이 디버깅 기능을 활성화하면 런타임은 할당 해제된 인스턴스를 즉시 제거하지 않고 특별한 좀비 객체로 바꾼다.
-
객체가 사용한 코어 메모리(core memory)는 재사용할 수 없게 한다.
그러므로 그 어느 것이라도 그 자리를 덮어쓸 수 없다.
좀비 객체가 메시지를 받는다면 좀비 객체로 보낸 메시지와 만일 아직 살아있었다면 사용되었을 객체에 대한 정확한 정보를 가진 예외를 던질 것이다.
좀비를 사용하는 것은 메모리 관리 오류를 디버깅하는 최고의 방법이다.
-
이 기능은 NSZombieEnable 환경 변수를 YES 로 설정하면 사용할 수 있다.
$ export NSZombieEnabled = “YES"
$ ./app
-
좀비한테 메시지를 보내면 콘솔로 메시지가 출력되고 앱은 종료될 것이다.
메시지는 다음과 같이 생겼다.
*** -[CFString respondsToSelector:]: message sent to deallocated instance 0x7ff9e9c080e0
-
엑스코드에 옵션을 체크하여 엑스코드로 앱을 실행했을 때 자동으로 환경 변수가 설정되게 하는 것도 가능하다.
이것을 하려면 앱 스킴 편집에서 'Run configuration’ 을 선택한 다음 Diagnostics 탭을 고르고 ‘Enable Zombie Objects’ 체크 박스를 선택하면 된다.
-
좀비 기능은 오브젝티브-C 런타임과 Foundation, CoreFoundation 프레임워크 깊숙한 곳에 구현되어 있다.
객체가 할당 해제될 때 이 기능을 활성화할지를 나타내는 환경 변수가 켜져 있으면
추가적인 단계(extra step)가 실행된다.
이 추가 단계는 객체를 완전히 할당 해제하는게 아니라 좀비로 만들어 준다.
-
메모리 버그는 ARC 를 사용해도 생길 수 있다.
-
객체의 클래스는 EOCClass 에서 _NSZombie_EOCClass 로 변경되었다.
컴파일러가 좀비 기능이 켜져 있을 때 컴파일하는 모든 클래스마다 추가로 클래스(좀비 클래스)를 하나 더 생성하는 것은 매우 비효율적일 것이다.
_NSZombie_EOCClass 가 실행 시간에 처음으로 생성되는 때는 처음으로 EOCClass 의 객체가 좀비로 바뀔 때다.
이것은 클래스 리스트를 조작할 수 있는 강력한 런타임 함수를 사용한다.
-
좀비 클래스는 _NSZombie_ 템플릿 클래스의 복사본이다.
이러한 좀비 클래스는 많은 일을 하지는 않고 간단한 표시자(marker) 로서 행동한다.
-
NSZombieEnabled 환경변수가 설정되어 있으면 런타임이 dealloc 메서드를 위의 코드 버전으로 스위즐링한다.
이 루틴의 끝에는 객체의 클래스가 _NSZomebie_OriginalClass 로 변경된다.
OriginalClass 는 원래 클래스의 이름이다.
-
결정적으로 객체가 거주한(live) 메모리는 해제되지 않는다. (free() 호출을 통해)
그렇기 때문에 그 메모리는 재사용이 불가능할 것이다.
메모리 누수이지만 이것은 디버깅 도구다.
그리고 실제 운영 중인 앱에서는 절대로 켜지지 않을 것이다.
그렇기 때문에 문제가 되지 않는다.
-
왜 클래스마다 좀비로 바뀔 새로운 클래스를 생성하는가?
이는 메시지가 좀비로 보내질 때 원래의 클래스가 결정되기 때문이다.
새로운 클래스는 런타임 함수 objc_duplicateClass() 를 이용해 생성한다.
클래스 전체를 복사하지만 새로운 이름을 부여한다.
상위 클래스, 인스턴스 변수, 메서드는 복사한 클래스와 복사된 클래스가 완전히 똑같다.
이전 클래스 이름을 유지하면서 새로운 클래스를 만드는 또 다른 방법은 복사하는 대신에 _NSZombie_ 로부터 상속받아 새로운 클래스를 만드는 것이다.
그러나 이 일을 하는 함수는 직접 복사하는 것에 비해 덜 효율적이다.
-
_NSZombie_ 클래스(그리고 그것의 모든 복사본)는 아무런 메서드도 구현하지 않는다.
이 클래스는 상위 클래스가 없다.
그리고 모든 최상위 클래스가 가져야 할 isa 라는 인스턴스 변수를 유일하게 가지고 있는 NSObject 와 같은 오브젝티브-C 최상위 클래스(root class)이다.
이 가벼운 클래스는 어떠한 메서드도 구현하지 않는다.
그래서 어떠한 메시지를 보내더라도 모두 완전 포워드 기술로 넘길 것이다.
완전 포워드 기술의 핵심은 __forwarding__ 이다.
이 함수가 하는 첫 번째 일은 메시지를 보낸 객체의 클래스 이름을 확인하는 것이다.
이름이 _NSZombie_ 로 시작하면 좀비가 발견된 것이다.
그리고 뭔가 특별한 일이 일어난다.
앱은 이 지점에서 메시지를 출력하고 죽는다.
-
객체의 원래 클래스를 명확하게 볼 수 있다.
또한 메시지를 보낸 죽은 객체의 포인터까지 볼 수 있다.
이 정보는 필요하다면 디버거에서 좀 더 분석하기 위해 사용할 수 있다.
또한 엑스코드에 있는 Instruments 같은 교정 도구(correct tool)의 유용함을 증명할 때 사용할 수도 있다.
기억할 점
객체가 할당 해제되었을 때 할당 해제되는 대신 좀비로 바꿀 수 있다.
이 기능은 NSZombieEnabled 환경 표식을 이용해 활성화 할 수 있다.
객체의 클래스를 특별한 좀비 클래스로 변경하기 위해 isa 포인터를 조작해서 객체를 좀비로 바꾼다.
좀비 클래스는 어떤 객체에 어떤 메시지를 보냈는지 알리는 메시지를 출력하고 앱을 종료함으로써 모든 선택자에 응답한다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #37 블록을 이해하라 (0) | 2017.10.01 |
---|---|
[Effective Objective-C] #36 retainCount 를 사용하지 말라 (0) | 2017.09.30 |
[Effective Objective-C] #34 오토릴리스 풀을 사용하여 최고 메모리 사용량을 낮춰라 (0) | 2017.09.28 |
[Effective Objective-C] #33 weak 참조를 사용하여 리테인 순환을 피하라 (0) | 2017.09.27 |
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #25 ~ #32 (0) | 2017.09.26 |
댓글