본문 바로가기
아이폰 개발/Objective C

NSObject 1

by 인생여희 2020. 12. 28.

NSObject 프로토콜 제공 메소드

 

 

1.인스턴스 명세

 

@property (readonly, copy) NSString *description

수신자 인스턴스 명세를 NSString * 형식으로 반환해 준다. 기본적으로 현재 인스턴스의 클래스 이름과 함께 인스턴스의 포인터를 반환 한다. 반면 같은 이름의 클래스 메소드 +(NSString) description은 기본적으로 클래스 이름만 반환한다.

 

 

2.메시지 전송, 메시지 실행

 

지정한 메시지를 수신자에게 발송하고 결과를 반환한다.

- (id)performSelector:(SEL)aSelector;

- (id)performSelector:(SEL)aSelector withObject:(id)object;

- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

 

 

3.MRC 관련 메소드 , ARC 사용을 추천하는 애플에서는 사용자제 권장

 

- (instancetype)retain OBJC_ARC_UNAVAILABLE;

수신자의 소유계수 1 증가

 

- (oneway void)release OBJC_ARC_UNAVAILABLE;

수신자의 소유계수 1 감소. 소유계수가 0에 도달하면 시스템은 dealloc 메시지 전송

 

- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;

AutoreleasePool 블록의 끝을 만나면 수신자의 소유계수를 1만큼 감소시킨다. 즉, release 메시지를 지연발생 시킨다. 주로 임시로 사용하거나 다른 메소드의 인자 또는 반환 객체와 같은 임시 객체에 사용된다.

 

- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

수신자의 소유계수를 반환한다. (디버깅용으로만 사용해야함)

 

 

4.클래스 / 인스턴스 확인

 

- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");

수신자가 속한 클래스 주소반환. 해당 수신자가 어떤 클래스에서 생성되었는지 알 수 있다.

 

@property (readonly) Class superclass;

수신자가 속한 클래스의 슈퍼클래스 주소를 반환한다.

 

- (instancetype)self;

수신자 인스턴스 자신의 주소를 반환한다. 메소드 self는 인스턴스 내부에서 자기를 가리키는 변수 self와 다르게 해석하는 것에 주의한다. 의미가 없는 동작 같지만 객체가 메소드를 처리할 수 있도록 클래스를 준비(initialize) 시키는데 사용한다.

 

5.인스턴스 내용비교

 

- (BOOL)isEqual:(id)object;

수신자의 내용이 인수 인스턴스의 내용과 동일한지 여부를 BOOL 방식으로 반환한다.

 

 

6.인스턴스의 클래스와 메소드 확인

 

 

- (BOOL)isKindOfClass:(Class)aClass;

수신자 인스턴스가 인수로 전달한 클래스 또는 자식 클래스에서 생성되었는지를 확인하여 BOOL 방식으로 반환한다. 상속관계를 고려하여 자식 클래스까지 확인한다. 

 

- (BOOL)isMemberOfClass:(Class)aClass;

수신자 인스턴스가 인수로 전달한 클래스에서 생성되었는지를 확인하여 BOOL 방식으로 반환한다. 위의 isKindOfClass와 달리 현재 클래스만 확인한다. 

 

- (BOOL)respondsToSelector:(SEL)aSelector;

수신자 인스턴스가 인수로 전달한 메소드에 응답하는지를 확인하여 BOOL 방식으로 반환한다.

 

#import <Foundation/Foundation.h>

@interface A : NSObject @end
@implementation A @end

@interface B : NSObject @end
@implementation B @end

@interface Aa : A
+(NSString *)description;
@end
@implementation Aa
+(NSString *)description
{
	// 이렇게 작성할 경우 description 무한루프 형성, 오버라이드 과정에서 [self description]이
	// 호출되면 다시 이 메소드가 호출되기 때문에 무한루프가 형성된다.
	// return [NSString stringWithFormat:@"%@ %@", [self superclass], self];
	return [NSString stringWithFormat:@"%@", [self superclass]];
}
@end

int main(int argc, const char * argv[])
{
	@autoreleasepool
	{
		Aa *a = [[Aa alloc] init];
		// instance 메소드, isMemberOfClass는 인수 클래스만 검색
		// isKindOfClass는 인수 클래스의 종속관계까지 검색
		printf("%d\n", [a isKindOfClass:[A class]]);			// 1
		printf("%d\n", [a isKindOfClass:[Aa class]]);			// 1
		printf("%d\n", [a isMemberOfClass:[A class]]);			// 0
		printf("%d\n", [a isMemberOfClass:[Aa class]]);			// 1
		
        // -(NSString *)description은 기존대로 인스턴스의 이름과 포인터 출력
		NSLog(@"%@", a); //<Aa: 0x10313df30>
		
        // +(NSString *)description은 위에서 오버라이드, 수퍼클래스의 description으로 대처
		NSLog(@"%@", [a class]); //Aa
		
		// 인스턴스 메소드
		// respondsToSelector:, isKindOfClass:, isMemberOfClass:
		// 클래스 메소드
		// isSubClassOfClass:
		printf("respondsToSelector: %d\n", [NSObject respondsToSelector:@selector(isKindOfClass:)]);
		printf("respondsToSelector: %d\n", [A respondsToSelector:@selector(isKindOfClass:)]);
		printf("respondsToSelector: %d\n", [Aa respondsToSelector:@selector(isKindOfClass:)]);
		printf("respondsToSelector: %d\n", [B respondsToSelector:@selector(isKindOfClass:)]);
		printf("isSubClassOfClass:  %d\n", [Aa isSubclassOfClass:[A class]]);		// 1
		printf("isKindOfClass:      %d\n", [A isKindOfClass:[NSObject class]]);		// 1, instance method의 리시버를 클래스
		printf("isKindOfClass:      %d\n", [Aa isKindOfClass:[NSObject class]]);	// 1, "
        
        
		printf("isKindOfClass:      %d\n", [Aa isKindOfClass:[A class]]);			// 0, ", 위는 1인데 이번엔 0?
		printf("isKindOfClass:      %d\n", [Aa isKindOfClass:[B class]]);			// 0, ", 로직상 0
		
        printf("isMemberOfClass:    %d\n", [A isMemberOfClass:[NSObject class]]);	// 0, ", 로직상 0
		printf("isSubClassOfClass:  %d\n", [B isSubclassOfClass:[A class]]);		// 0
		
        [a release];
		
 
	}
    return 0;
}

 

결과

 

 

 

시스템에서 제공하는 클래스를 적용했을때

 

#import <Foundation/Foundation.h>


int main(int argc, const char * argv[])
{
	@autoreleasepool
	{

        
		id str = [[NSString alloc] initWithUTF8String:"Hello"];
		
		printf("%d\n", [str isKindOfClass:[NSObject class]]);	// 1
		printf("%d\n", [str isKindOfClass:[NSString class]]);	// 1
		
        printf("%d\n", [str isMemberOfClass:[NSObject class]]);	// 0
        
        
        /*
         isKindOfClass: 와 isMemberOfClass: 는 현재 인스턴스가 소속된 클래스를 점검하는데 유용하게 사용될 수 있다.
         하지만, 시스템에서 제공하는 기초 프레임워크의 클래스를 적용했을 때, 의외의 결과가 나올 수 있다.
         str은 NSString에서 상속된 NSTaggedPointerString 클래스의 인스턴스 이기 때문이다.
         */
        
		printf("%d\n", [str isMemberOfClass:[NSString class]]);	// 0  ?
		
        // Hello NSTaggedPointerString NSString 순서로 출력된다. 위의 str은 실제 NSString의 서브클래스
		NSLog(@"%@ %@ %@", [str self], [str class], [NSString class]);
		
        
        [str release];
		
		NSNumber *aBool = [NSNumber numberWithBool:YES];
		NSNumber *anInt = [NSNumber numberWithInt:1];
		NSNumber *anInteger = [NSNumber numberWithInteger:10];
		NSLog(@"%@", [aBool class]);		// NSCFBoolean
		NSLog(@"%@", [anInt class]);		// NSCFNumber
		NSLog(@"%@", [anInteger class]);	// NSCFNumber
	}
    return 0;
}

 

 

 

NSObject 클래스 제공 메소드

 

1.초기화

 

+ (void)initialize;

사용자가 클래스 멤버 변수를 초기화 하기 위해 재지정하는 클래스 메소드로, 첫 메시지를 받기 전에 클래스를 초기화 한다. 사용자가 오버라이드 하여 재정의 하지만 직접 호출하면 안된다. 오브라이드를 작서할 때 내부에서 슈퍼클래스의 initialize를 호출하는 것도 금지된다.

 

 

2.인스턴스 생성, 복사, 해제

 

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

수신자 클래스에서 새로운 인스턴스를 생성하고 주소값을 반환 한다.

 

- (instancetype)init

alloc 클래스 메소드로 생성된 인스턴스를 초기화한다. 보통 서브클래스에서 멤버변수에 따라 오버라이딩 재정의하여 사용한다. 재정의할때는 상위 클래스의 init을 호출하여야 한다.

 

- (id)copy;

수신자 인스턴스의 복사본을 생성하여 그 주소를 반환한다. 실제 이 메소드는 <NSCopying> 프로토콜에서 정의된 -(id)copyWithZone: 을 호출하여 수행되며, 오버라이드도 프로토콜 메소드를 재정의하여야 한다.

 

- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

수신자 인스턴스가 메모리에서 해제될 때 수행해야할 작업을 사용자가 지정한다. 소유계수가 0이 될 때, 시스템에 의해 호출되기 때문에 사용자가 직접 호출해서는 안된다.

 

+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");

클래스 이름으로 지정된 수신자를 통해 클래스 객체를 반환한다. 프로그램 중 NSObject라고 클래스 이름만 쓰면 클래스 형식을 뜻하고, [NSObject class] 라고 쓰면, 클래스 객체의 주소를 의미한다.

 

+ (Class)superclass;

수신자 클래스의 슈퍼 클래스 객체를 반환한다.

 

+ (BOOL)isSubclassOfClass:(Class)aClass;

수신자 클래스가 인수 클래스의 서브클래스인지 확인하여 결과를 BOOL 방식으로 반환한다. isSubclassOfClass 는 인스턴스가 어떤 클래스에서 만들어졌는가를 보는 것이 아니라 클래스 간의 종속 여부를 점검할 때 사용한다. 클래스간의 종속 여부는 설계중에 결정되기 때문에 이 기능은 자주 사용되지 않는다. 하지만 리시버를 클래스 객체로 하는 클래스 메소드와 리시버를 인스턴스 메소드로 하는 인스턴스 메소드의 활용 차이만 기억하고 있으면 된다.

 

//Aa 클래스는 A의 자식이다

//B 클래스는 A의 자식이 아니다.

printf("isSubClassOfClass:    %d\n", [Aa isSubclassOfClass:[A class]]); // 1

printf("isSubClassOfClass:  %d\n", [B isSubclassOfClass:[A class]]); // 0

 

+ (NSString *)description;

수신자 클래스의 명세를 NSString * 형식으로 반환해 준다.

 

 

클래스 객체 

 

- (BOOL)isKindOfClass:(Class)aClass 는 인스턴스 메소드로 리시버를 인스턴스로 사용해야 한다. 만약 아래와 같이 기술한다면?

 

printf("%d\n", [A isKindOfClass:[NSObject class]]); // 1

 

 

위의 로직은 컴파일 오류가 발생될 것 같은데, 빌드도 되고, 실행도 된다. 이런 결과가 나오는 이유는 클래스 객체 역시 특정한 상위 개념 클래스(메타 클래스)의 인스턴스 객체이기 때문이다.

 

아래 그림에서 Is a는 자신의 설계(클래스)를 가진 구조체를 지칭하는 포인터이다. 또한 super class는 각설계에서 부모 형틀을 지시하는 포인터다. 이 포인터를 통해 자신의 클래스에 없는 멤버 변수 또는 메소드를 검색해 나간다.

 

 

 

 

위에서 언급한것 처럼 NSObject 인스턴스 객체는 isa 변수만 가지고 있는 빈깡통 상태다. 이 isa 변수는 자신을 생성한 형틀 클래스에 관련된 중요한 정보를 제공해 주고 있다. 

 

 

 

 

위의 구조에서 알수 있는 점은 클래스 객체도 인스턴스 객체와 같이 Class isa 포인터를 통해 자신의 형틀 클래스에 대한 정보를 가지고 있다. 또한 인스턴스 메소드(-)는 실제로는 클래스 객체에 포함되어 있으며 클래스 메소드(+)는 실제로는 메타클래스가 가지고 있다.

 

 

 

위의 구조도를 보면 각 인스턴스의 멤버변수는 각각의 인스턴스 객체에 저장되고, 클래스 멤버변수는 각각의 클래스 객체에 저장되는것을 알 수 있다. 반면에 인스턴스 메소드 (-)는 클래스 객체에 저장되고, 클래스 메소드 (+)는 메타 클래스 객체에 저장되어 관리된다. 왜그럴까?

 

이유는 멤버변수는 각 객체마다 고유의 값을 가지고 있어야 하지만, 메소드는 각 객체마다 따로 저장할 필요가 없이 공유가 가능하기 때문이다. 이런 이유로 멤버변수는 각 객체(인스턴스, 클래스, 메타클래스)와 같은 계층에 저장되지만 메소드는 각 객체보다 한 단계 위의 계층에 저장되는 것이다.

 

이렇게 구성된 메타 클래스 > 클래스 > 인스턴스의 구조로 인해 위에서 알아본것과 같이 인스턴스 메소드를 클래스 객체로 보내 실행하는 것이 가능하게 된다.

 

printf("%d\n", [A isKindOfClass:[NSObject class]]); // 1

 

그러나 이 경우 그 결과가 예상과 다르게 나올 수 있기 때문에 리시버와 메소드 선택에 주의가 필요하다.

 

 

참고

 

 

objc 는 클래스 객체라는 개념을 실시간에 활용할 수 있게하여 관리에 많은 유동성과 기능을 부여한다. 하지만 간혹 위 사진의 예제 같은 오류에는 대처하지 못하는 문제가 있다.

 

 

 

 

 

'아이폰 개발 > Objective C' 카테고리의 다른 글

objective c 카테고리  (0) 2020.12.29
NSObject 2  (0) 2020.12.28
objective c 소유권  (0) 2020.12.27
objective c 동적결합  (0) 2020.12.26
objective c 상속과 클래스  (0) 2020.12.25