[Effective Objective-C] #14 클래스 객체가 무엇인지 이해하라
출처 : Effective Objective-C
-
객체 타입은 컴파일 시간에 결정되지 않고 실행 시간에 찾는다.
-
컴파일러는 리시버가 이해할 수 없다고 생각되는 메시지를 보내는 것에 경고를 보낼 수 있다.
반대로 id 타입의 객체는 모든 메시지에 응답할 수 있다고 여긴다.
-
실행 시간에 객체 타입을 알아내는 것을 내성(introspection)이라 하고, 이는 Foundation 프레임워크의 NSObject 의 프로토콜로 녹아 들어간 강렬하고 유용한 기능이다.
이 프로토콜은 모든 공통 최상위 클래스(NSObject 와 NSProxy) 를 상속받는 모든 객체가 따르는 프로토콜이다.
객체의 클래스를 직접 비교하는 것보다는 이런 메서드를 이용하는 것이 신중한 행동일 것이다.
-
모든 오브젝티브-C 객체 인스턴스는 메모리 블랍(blob)의 포인터다.
-
객체를 위한 메모리를 스택에 할당하려 한다면 컴파일러 에러가 날 것이다.
-
제네릭 객체 타입(generic object type)인 id는 이미 그 자체가 포인터다.
그래서 *을 붙여 선언할 필요가 없다.
-
정확한 타입을 명시하는 것과 id 방식을 사용하는 것의 유일한 차이점은 클래스의 인스턴스에 없는 메서드를 호출하려 할 때
컴파일러가 경고를 줄 수 있다는 것이다.
-
모든 객체의 메타데이터를 저장하는 데이터 구조체는 id 타입 자체의 정의와 함께 런타임 헤더에 정의된다.
typedef struct objc_object{
Class isa;
} *id;
즉 어떤 인스턴스에 isa 를 호출하면 해당 Class 에 대한 정보를 던져준다.
-
모든 객체의 첫 번째 맴버 변수로 Class 타입의 변수를 포함한다.
이 변수는 객체의 클래스를 정의하고 가끔 ‘is a’ 포인터로 참조된다.
typedef struct objc_class *Class;
struct objc_class{
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
이 구조체는 클래스의 메타데이터를 포함한다.
클래스의 인스턴스가 구현한 메서드들과 인스턴스가 가진 인스턴스 변수들에 대한 것이다.
-
super_class 라는 변수도 갖고 있다.
이 변수는 클래스의 부모를 정의한다.
-
클래스의 타입(예를 들어 isa 포인터가 가리키는 클래스)은 metaclass 라는 또 다른 클래스다.
이 클래스는 클래스 자체의 인스턴스에 대한 메타데이터를 표현하는 데 사용되고, 클래스 메서드가 정의되어 있는 곳이다.
그 메서드들이 클래스 인스턴스의 인스턴스 메서드라 여겨지기 때문이다.
-
클래스의 인스턴스와 그와 연관된 메타클래스의 인스턴스는 오직 한 개만 존재한다.
-
super_class 포인터는 계층 구조를 만들어 내고, isa 포인터는 인스턴스의 타입을 나타낸다.
내성을 수행하기 위해 이 구조체를 이용할 수 있다.
클래스 계층도 살펴보기
-
내성 메서드는 클래스 계층도를 알아내는 데 사용할 수 있다.
isMemberOfClass: 를 이용해 객체가 어떤 클래스의 인스턴스인지 알아내거나 isKindOfClass: 를 이용해 객체가 특정 클래스 또는 그 클래스의 상속 계층 클래스의 인스턴스인지 알아낼 수 있다.
-
객체의 클래스를 얻기 위해 isa 포인터를 활용하고 super_class 포인터를 이용해 클래스 상속 계층을 따라 올라갈 수 있다.
객체가 동적이기 때문에 이런 기능은 매우 중요하다.
-
보통 컬렉션에서 객체를 꺼낼 때 내성을 사용한다.
맴버 객체들이 강한 타입(strong type)이 아니기 때문이다.
이는 객체가 컬렉션에서 꺼낸 객체는 id 타입을 사용한다는 것을 의미한다.
타입을 알아내야 할 때 내성이 사용될 수 있다.
-
내성 클래스 대신에 object == [SomeObject class] 와 같이 == 연산자를 사용할 수도 있다.
그렇게 할 수 있는 이유는 클래스 자체가 싱글턴이기 때문이다.
모든 클래스의 Class 객체의 인스턴스는 앱에서 오직 한 개만 있다.
-
클래스 객체의 동등을 직접 비교하기보다는 내성 메서드를 항상 사용해야 한다.
내성 메서드가 메시지 포워딩을 사용하는 객체도 비교할 수 있기 때문이다.
모든 선택자를 다른 객체로 포워드하는 객체를 생각해 보자.
그런 객체를 프락시(proxy)라 하고 NSProxy 는 프락시 객체의 최상위 클래스다.
프락시 객체에 class 메서드를 호출하면 프락시된 객체(proxied object) 클래스가 아닌 프락시 클래스( 예를 들면 NSProxy 의 하위 클래스 ) 가 반환될 것이다.
그러나 isKindOfClass: 같은 내석 메서드를 프락시 객체에 호출하면 프락시된 객체에 메시지를 전달할 것이다.
이는 메시지에 대한 응답을 프락시된 객체가 한다는 것을 뜻한다.
그렇기 때문에 class 메서드를 호출해서 받은 반환값과는 다를 것이다.
기억할 점
클래스 계층은 Class 객체들로 구성되어 있다.
각 Class 인스턴스는 타입을 정의하는 포인터를 가지고 있다.
객체 타입을 컴파일 시간에 알 수 없을 때 내성을 꼭 사용해야 한다.
클래스 객체를 직접 비교하는 것보다는 가능한 한 내성 메서드를 사용하라.
객체가 메시지 포워딩을 구현했을 수 있기 때문이다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #16 지정 초기화 메서드를 만들라 (0) | 2017.08.18 |
---|---|
[Effective Objective-C] #15 접두어를 사용해 네임스페이스 충돌을 피하라 (0) | 2017.08.17 |
[Effective Objective-C] #13 불투명 메서드를 디버깅할 때 메서드 스위즐링을 사용하라 (0) | 2017.08.15 |
[Effective Objective-C] #12 메시지 포워딩을 이해하라 (0) | 2017.08.14 |
[Effective Objective-C] #11 objc_msgSend 의 역할을 이해하라 (0) | 2017.08.13 |
댓글