Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
iOS Developer/Objective-C

20. Objective-C Category, Posing, Extension

by Toughie 2024. 5. 22.

KAKAO-Choonsik

 

[Category, Posing, Extension]

 

이번에는 클래스를 확장할 수 있는 기능들에 대해서 알아보자.


Category

기본 클래스에 나만의 기능을 추가하는 등

특정 상황에서 이미 존재하는 클래스에 기능을 추가하고 싶은 경우에  Category를 사용할 수 있다.

(변수는 추가할 수 없다.)

@interface 클래스명 (카테고리명)
//method declare
@end

 

카테고리는 원본 클래스를 수정하지 않고, 어떤 클래스에서든 선언할 수 있다.

카테고리에서 선언한 메서드는 원본 클래스의 모든 인스턴스에서 호출 가능하다.(자식 클래스 포함)

런타임에서 카테고리를 통해 추가된 메서드와 원본 클래스에서 구현된 메서드는 동일한 방식으로 동작한다.

(다만 이름이 동일할 경우 메서드끼리 덮어씌워지는 등 충돌할 수 있어 주의해야 한다.)

 

헤더파일에서 카테고리 추가

//myHeader.h 헤더파일로 선언부 분리
#import <Foundation/Foundation.h>

@interface NSString (MyCategory)
+(NSString *) getMyName;
@end

 

구현파일에서 카테고리 구현 및 사용

#import "myHeader.h"

@implementation NSString (MyCategory)
+(NSString *) getMyName {
    return @"TOUGHIE";
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *myName = [NSString getMyName];
        NSLog(@"My name is %@",myNamne);
    }
    return 0;
}

 

메서드 앞에 +를 붙여 클래스 자체에 속하고 객체 인스턴스에 의존하지 않는 정적(static) 메서드로 정의했다.

인스턴스를 생성하지 않고도 클래스 이름을 통해 직접 호출할 수 있다.


Posing

한 클래스가 다른 클래스의 포즈를 취해서 마치 자기가 다른 클래스의 인스턴스 인 것처럼 하는 기능

(네이밍이 재밌다 ㅋㅋ)

(커스텀뷰 클래스를  NSView 처럼 사용하기 위하는 등)

하지만 디버깅이 어렵고 런타임에 클래스의 메서드 동작이 변하는 것이 안정적이지 못해서

아예 deprecated 되었고 이제는 Method Swizzling으로 대체되었다.

주로 카테고리를 이용해 기존 메서드를 새로운 메서드로 교체하는 방식이다.

 

UI 코드에서 많이 쓰이는 것 같은데 대략 이런 기능도 있었고 지금은 Swizzling으로 대체되었다는 것만 알고

나중에 필요할 때 다시 찾아볼 예정이다.


Extension(확장)

extension은 컴파일 시점에 소스코드가 존재하는 클래스에만 추가가 가능하다.

extension은 컴파일 시점에 클래스의 일부로 같이 취급되기 때문이다.

 

확장은 반드시 구현부가 있는 클래스에만 추가가 가능하며 (구현 파일에서 확장해야 한다.)

주로 private method나 private variable을 추가하기 위해 사용된다.

확장을 통해 추가된 멤버들은 자식 클래스에도 접근할 수 없다. 

(완전 자신만을 위한 추가요소라고 생각할 수 있다.)

 

@interface 클래스명 ()

@end

 

확장 문법은 카테고리랑 유사한데 카테고리명이 없어서 익명카테고리라고 부르기도 한다.

외부에 헤더파일에 따로 존재하는 것도 아니고, 구현 파일에서만 작성하고 사용되기 때문에

이름이 필요없기 때문이다.


구현 파일 하나에서 예시 코드를 전부 작성해 버리면(모든 클래스와 main까지) 다 접근이 가능해져서

헤더파일과 구현파일을 분리해서 확인해 보았다.

 

결론은 extension을 통해 추가하는 녀석들은 해당 구현 파일 안에서만 접근하고 사용할 수 있다는 것이다.

private이기 때문에 외부 파일에서는 접근하지 못한다!

그냥 구현 파일 안에서 추가하고 사용하는 것이다.

 

MyClass.h

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
- (id) init;
- (void)publicMethod;

@end

 

MyClass.m

#import "MyClass.h"

// 익스텐션 선언
@interface MyClass ()

@property NSString *privateProperty;

- (void)privateMethod;

@end

@implementation MyClass

- (id) init {
    self = [super init];
    if (self) {
        _privateProperty = @"Initial Value";
    }
    return self;
}

- (void)publicMethod {
    NSLog(@"This is a public method");
     //구현파일 내부라서 extension에서 추가한 privateMethod 호출 가능
    [self privateMethod];
    //extension으로 추가한 프로퍼티에도 접근 가능
    NSLog(@"Private property value: %@", self.privateProperty);
}

- (void)privateMethod {
    NSLog(@"This is a private method");
    self.privateProperty = @"Updated Value";
}

@end

 

Subclass.h

#import "MyClass.h"

@interface SubClass : MyClass
@end

그냥 상속만 해주었다.

 

Subclass.m

#import "SubClass.h"

@implementation SubClass

//상속 관계지만 부모의 extension에서 추가된 프로퍼티(와 메서드에) 접근할 수 없다..

@end

 

main.m

#import <Foundation/Foundation.h>
#import "MyClass.h"
#import "SubClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myObject = [[MyClass alloc] init];
        [myObject publicMethod];
        
        //[myObject privateMethod];
        //NSLog(@"%@",myObject.privateProperty);
        
        SubClass *subObject = [[SubClass alloc] init];
        [subClass publicMethod];
        //[subClass privateMethod];
    }
    return 0;
}

이거 진짜 private인가? 

extension으로 추가하면 프로퍼티와 메서드가

전부 private해서 외부에서 접근이 절대 안 돼!라고는 말하기 좀 애매한 게..

 

프로퍼티는 확실히 외부 파일에서 접근이 안되는데(자식 클래스에서도)

extension에서 추가한 메서드 같은 경우 자동완성에는 안 뜨지만 직접 타이핑하면 호출은 된다.

물론 헤더파일에 메서드 선언이 없기 때문에 

Instance method '-privateMethod' not found (return type defaults to 'id')라는 경고가 뜨지만..


'쓰지 말라는 의도'

결론적으로 extension은 private 멤버를 추가하는 용도로 사용하기는 하지만

완벽한 프라이빗은 아니다. 해당 클래스의 구현 파일에서만 사용하도록 '의도'하는 것이지만

사실 외부에서 접근이 가능할 수도 있다는 말이다. (당연히 권장되지는 않는다.) 

 

익스텐션에서 정의된 프로퍼티는 해당 클래스의 구현 파일 내부에서만 사용 가능하고

외부에서는 정말 접근할 수 없다. (캡슐화가 잘 적용된다.)

하지만 메서드의 경우 동적 바인딩이 이루어지기 때문에 메서드가 정의되어 있다면 

외부에서도 사실 호출이 되는 것이다. 하지만 외부에서 호출하지 않는 것이 개발자의 의도이기 때문에 

자동완성에는 뜨지 않는 것이다. (publicMethod는 자동완성에 잘 뜬다.)


결국 이런 접근제어를 통한 은닉화 등은 다른 개발자와 협업하거나 클라이언트가 사용하는 상황에서

숨길 것들을 숨기고 노출시킬 것들만 노출시키는 것이 가장 큰 의도이기 때문에 

클래스 내부적으로 사용할 것들은 extension을 통해서 추가시켜 주면 된다.

 

기어코 자동완성도 안되는데 외부에서 접근해서 사용하는 것은 음.. 

메서드 호출이 컴파일 타임이 아니라 런타임에 결정되는 동적 메시징의 한계라고 볼 수 있을 듯.

 

메서드를 어디서든 다 사용하게 하고 싶으면 category를 사용하도록 하고,

클래스 구현 내부로직에만 사용이 필요한 경우에는 extension을 사용해 보도록 하자.