본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라

by 돼지왕 왕돼지 2017. 9. 29.
반응형

 [Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라


출처 : Effective Objective-C

arc, core memory, corefoundation, correct tool, dealloc, Diagnostics, enable zombie objects, Foundation, Free, Instruments, ISA, marker, message sent to deallocated instance, NSZombieEnable, objc_duplicateClass, respondsToSelector, root class, Run configuration, Runtime, xcode, Zombie, [Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라, _nszombie, __forwarding__, 교정 도구, 덮어쓰기, 디버깅, 디버깅 도구, 런타임, 런타임 함수, 메모리 관리 오류, 메모리 누수, 메모리 버그, 메모리 일부분 재사용, 메모리 재사용 여부 알 수 있는 방법, 메모리 해제, 새로운 이름 부여, 스위즐링, 앱 스킴 편집, 엑스코드, 예외, 완전 포워드 기술, 좀비, 좀비 메모리, 좀비 메세지, 체크 박스, 최상위 클래스, 코어 메모리, 코코아, 크래시, 클래스 복사, 템플릿 클래스, 할당 해제, 할당 해제된 인스턴스 좀비 객체 전환, 환경 변수


-

메모리 관리 오류를 디버깅하는 것은 매우 어려운 일이다.

할당 해제된 객체에 메시지를 보내는 일은 예상할 수 있듯이 전혀 안전하지 못하다.

그러나 때때로 그것은 동작하기도 하고 동작하지 않기도 하는데 이는 객체가 사용했던 메모리를 덮어 썼느냐에 달려있다.

메모리가 재사용되었는지 알 수 있는 방법은 없다.

그로 인해 크래시가 가끔 일어난다.

메모리의 일부분만 재사용되는 경우도 있는데 이 때 객체의 일부분은 여전히 유효하다.

또한 순전히 운으로 또 다른 유효한 살아 있는 객체가 메모리를 덮어쓸 수도 있다.

이 경우 런타임은 새로운 객체에 메시지를 보낼 것이고 객체는 응답을 할 수도, 하지 않을 수도 있다.

이 경우 크래시는 발생하지 않겠지만, 왜 메시지를 보낸 객체가 여러분의 의도대도 동작하지 않는지 알 수 없을 것이다.

객체가 선택자에 응답하지 않으면 앱은 보통 크래시된다.



-

운 좋게도 코코아에는 손쉽게 사용할 수 있는 ‘좀비(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 포인터를 조작해서 객체를 좀비로 바꾼다.

좀비 클래스는 어떤 객체에 어떤 메시지를 보냈는지 알리는 메시지를 출력하고 앱을 종료함으로써 모든 선택자에 응답한다.




반응형

댓글