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

[Effective Objective-C] #14 클래스 객체가 무엇인지 이해하라

by 돼지왕 왕돼지 2017. 8. 16.
반응형

 [Effective Objective-C] #14 클래스 객체가 무엇인지 이해하라


출처 : Effective Objective-C

==, blob 포인터, compile time, foundation 프레임워크, generic object type, ID, id *, instance_size, introspection, ISA, iskindofclass, isMemberOfClass, ivars, metaclass, nsobject, nsproxy, objc_cache, objc_ivar_list, objc_object, objc_protocol_list, objec_method_list, Runtime, strong type, struct, super_class, typedef, Version, [Effective Objective-C] #14 클래스 객체가 무엇인지 이해하라, 내성, 동적, 리시버, 메모리 블랍, 메시지 포워딩, 메타데이터, 메타클래스 인스턴스, 스택, 실행 시간, 실행 시간에 객체 타입 정의, 싱글턴, 최상위 클래스, 컬렉션, 컴파일 시간, 컴파일러 경고, 클래스 객체, 클래스 계층도 살펴보기, 클래스 메서드, 클래스 싱글턴, 클래스 인스턴스, 클래스 자체의 인스턴스에 대한 메타데이터, 프락시


-

객체 타입은 컴파일 시간에 결정되지 않고 실행 시간에 찾는다.



-

컴파일러는 리시버가 이해할 수 없다고 생각되는 메시지를 보내는 것에 경고를 보낼 수 있다.

반대로 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 인스턴스는 타입을 정의하는 포인터를 가지고 있다.


객체 타입을 컴파일 시간에 알 수 없을 때 내성을 꼭 사용해야 한다.


클래스 객체를 직접 비교하는 것보다는 가능한 한 내성 메서드를 사용하라.

객체가 메시지 포워딩을 구현했을 수 있기 때문이다.




반응형

댓글