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

objective c 카테고리

by 인생여희 2020. 12. 29.

objective c 카테고리

 

카테고리는 본래 메소드를 기능별로 나누어 개발하기 위해 제공된 기능이다. 카테고리의 기능은 본래 분할 개발을 위해 나온 것이지만, 실제 환경에서는 이를 이용해서 기존 클래스에 추가 메소드를 정의할 수 있게 되었다. 메소드를 추가 정의하는 데는 클래스 메소드와 인스턴스 메소드 모두 가능하지만 추가로 멤버변수를 선언하는 것은 불가능하다. 카테고리는 특정 클래스에 종속적이기 때문에 처음 선언 부터 확장할 클래스의 이름과 괄호로 감싼 카테고리 이름을 적어주면 된다.

 

 

카테고리는 클래스의 확장이다

 

예제

 

1. 카테고리의 인터페이스는 자신이 속한 클래스의 인터페이스를 임포트한다.

2. 카테고리의 구현파일은 자기의 카테고리의 인터페이스를 임포트한다.

3. 카테고리에 포함된 메소드를 사용할 경우 카테고리 인터페이스를 임포트한다. 이때 클래스 인터페이스 임포트는 1번에 의해 생략될 수 있다.

 

 

 

NSObject+Etc.h

 

#import <Foundation/Foundation.h>

@interface NSObject (Etc)
-(NSString *)print;
@end

 

NSObject+Etc.m

 

#import "NSObject+Etc.h"

@implementation NSObject (Etc)
-(NSString *)print
{
    //간편 생성자 - autoreleasePool에 들어가서 사용후 사라진다.
	return [NSString stringWithFormat:@"self:%p [self class]:%p\n", self, [self class]];
    
    /*
     
     self는 인스턴스 객체이기 때문에 주소가 서로 다르다.
     
     [self class] 는 클래스의 주소이기 때문에 같다.
     
     self:0x10392bcf0 [self class]:0x7fffabb84140

     self:0x10392bb00 [self class]:0x7fffabb84140
     */
}
@end

 

main.m

 

#import <Foundation/Foundation.h>

#import "NSObject+Etc.h"



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

{

@autoreleasepool

{

//클래스는 같지만 서로 다른 인스턴스 o, p

NSObject *o = [[NSObject alloc] init];

NSObject *p = [[NSObject alloc] init];



printf("%s\n", [[o print] UTF8String]);

printf("%s\n", [[p print] UTF8String]);

[o release];

[p release];

}

    return 0;

}

/*
     
     self는 인스턴스 객체이기 때문에 주소가 서로 다르다.
     
     [self class] 는 클래스의 주소이기 때문에 같다.
     
     self:0x10392bcf0 [self class]:0x7fffabb84140

     self:0x10392bb00 [self class]:0x7fffabb84140
     */

 

참고

 

 

 

카테고리 프라이빗 메소드

 

A.h

 

#import <Foundation/Foundation.h>

@interface A : NSObject
-(NSString *)doPublic;
@end

 

A.m

 

#import "A.h"
#import "A+Name.h"

@implementation A
-(NSString *)doPublic
{
    
    //_doPrivate을 사적으로 변경한 경우. doPublic 내부에서 _doPrivate을 호출하고 결과를 합쳐 반환한다.
	return [[NSString stringWithFormat:@"self:%p class:%p ", self, [self class]] stringByAppendingString:[self _doPrivate]];
	//return [NSString stringWithFormat:@"self:%p class:%p in doPublic A클래스", self, [self class]];
}
@end

 

 

A+Name.h

 

//클래스 헤더 임포트
#import "A.h"

@interface A (Name)
-(NSString *)_doPrivate;
@end

 

 

A+Name.m

 

//카테고리 헤더 임포트
#import "A+Name.h"

@implementation A (Name)
-(NSString *)_doPrivate
{
	return [NSString stringWithFormat:@"class:%p name:%s (name) 카테고리 in_doPrivage 메소드",
			[self class],
			[NSStringFromClass([self class]) UTF8String]];
    
    //NSStringFromClass : 클래스 이름을 반환
}
@end

 

 

main.m

 

#import <Foundation/Foundation.h>
#import "A.h"

//카테고리를 프라이빗하게 바꾸는 경우 생략가능
#import "A+Name.h"

// A.h
// A.m
// A+Name.h
// A+Name.m

int main(int argc, const char * argv[])
{
	@autoreleasepool
	{
		A *a = [[A alloc] init];
		
        //a 인스턴스의 공유 메소드 호출 (공유 메소드 내부에서 프라이빗 메소드도 호출)
        printf("%s\n", [[a doPublic] UTF8String]);
		
        //프라이빗 메소드 바로 호출
        //카테고리를 사적으로 바꿀 경우, 접근이 불가
        //대신 doPublic에서 _doPrivate을 호출하여 결과를 합쳐 반환.
        printf("%s\n", [[a _doPrivate] UTF8String]);
		
        [a release];
	}
    return 0;
}

 

 

결과

 

self:0x100598990 class:0x100004628 class:0x100004628 name:A (name) 카테고리 in_doPrivage 메소드
class:0x100004628 name:A (name) 카테고리 in_doPrivage 메소드

 

 

위 예제에서는 클래스 A와 함께 카테고리 A+Name을 사용했다. A에 있는 메소드 -(NSString*)doPublic은 인스턴스 포인터와 isa 클래스의 포인터 정보를 표시한다. A+Name에 있는 메소드 -(NSString*)_doPrivate은 클래스 포인터 정보와 클래스 이름을 표시한다. 카테고리에서 참고하는 클래스 역시 같은 클래스 A라는 것을 할 수 있다. 

 

참고로 위의 예제에서는 메소드 이름 규칙을 위반한다. ( _ ) 로 시작하는 메소드나 변수이름은 클래스 외부에 공개되어서는 안된다. 사적 메서드로 바꾸고 doPublic에의해 호출되도록 해야한다. 

 

 

카테고리 오버라이드

 

카테고리를 통해 기존 메소드를 재정의하게 되면 어떻게 될까? 이 경우 기존의 메소드에는 접근이 불가능해진다. 또한 해당 카테고리의 헤더파일을 임포트하지 않고 기존 클래스의 헤더파일만 임포트하더라도 카테고리에서 지정된 메소드가 호출되는 결과가 나타난다.

 

카테고리는 클래스 메소드의 확장으로 기존 클래스와 같은 계층을 가지게 된다. 즉, 카테고리에서 [super ..]라고 쓰게 되면 상위계층 NSObject가 되며 [self...]라고 쓰면 동일한 계층의 클래스 A의 카테고리 (Desc)가 된다.

 

 

그렇다면 다음과 같이 클래스 A와 카테고리 Desc 에서 같은 이름의 메소드를 지정하였을 때는 어떤 메소드가 선택될까?  objc에서는 이런경우 카테고리 메소드를 우선적으로 호출 한다. 또한 Desc 내부에 정의된 description에서 [self description]을 호출하게 되면 계속적인 재귀호출이 발생하면서 프로그램은 스택오버플로우 에러가 발생되고 실행은 중단된다.

 

 

 

 

 

카테고리 재정의 예제

 

A.h

 

#import <Foundation/Foundation.h>

@interface A : NSObject
-(NSString *)description;	// Overrided, 접근이 불가능해짐
@end

 

 

A.m

 

#import "A.h"

@implementation A
-(NSString *)description
{
    //description 재정의(override) 했는데,
    //카테고리에서 다시 재정의해서 메소드 접근 불가능.
	return [NSString stringWithFormat:@"My Description : [%s %p]", [NSStringFromClass([self class]) UTF8String], self];
}
@end

 

 

A+Desc.h

 

#import "A.h"

@interface A (Desc)
-(NSString *)description;	// Override
@end

 

 

A+Desc.m

 

#import "A+Desc.h"

@implementation A (Desc)
-(NSString *)description
{
    
    /*
     카테고리에서 지칭하는 super는 클래스 내부에서 쓰는것과 동일하다.
     즉, 여기서는 NSObject를 뜻한다.
     또한 self 역시 자신의 클래스 + 카테고리를 뜻한다.
     [self description]의 호출은 재귀호출 생성
     */
    
//	return [super description];		// invoke NSObject -(NSString *)description;
//	return [self description];		// invoke category's description > stack overflow
    
    //카테고리에서 다시 재정의, 실제 동작은 클래스의 메소드와 거의 동일. Category라는 단어만 바뀜
	return [NSString stringWithFormat:@"Category Description : [%s %p]", [NSStringFromClass([self class]) UTF8String], self];
}
@end

 

 

main.m

 

#import <Foundation/Foundation.h>

//기존 클래스에 정의된 메소드를 재정의한 경우 카테고리 헤더를 임포트 할 필요 없다.
#import "A.h"

int main(int argc, const char * argv[])
{
	@autoreleasepool
	{
		A *a = [[A alloc] init];
        
        //description 호출됨
        //카테고리에 재정의된 description 메소드 호출됨.
		NSLog(@"%@", a);
	}
    return 0;
}


 

 

결과

 

//결과
//2020-12-29 17:49:48.500525+0900 0501-category override[4350:239608] Category Description : [A 0x10050ac30]

 

 

 

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

objective c 클래스 클러스터의 서브 클래스  (0) 2020.12.30
objective c 클래스 클러스터(추상클래스)  (0) 2020.12.29
NSObject 2  (0) 2020.12.28
NSObject 1  (0) 2020.12.28
objective c 소유권  (0) 2020.12.27