[Objective-C] 클래스,인스턴스 메소드를 동적으로 추가하기

동적 메소드 변형

메소드를 동적으로 추가하기

(도대체 언제인지는 감을 잡기 힘들지만) 객체에 동적으로 기능의 구현을 추가해야 할 경우가 이따금씩 있다. 예를 들자면 @dynamic 지시어를 써서 프로퍼티를 선언하는 경우가 이에 해당한다.

@dynamic propertyName;

이 구문은 컴파일러에게 프로퍼티와 연관되는 메소드가 동적으로 제공된다는 것을 알려주게 된다.

이러한 동적 메소드 할당을 위해서는 resolveInstanceMethod:resolveClassMethod:를 구현해서 특정 셀렉터를 클래스 메소드 혹은 인스턴스 메소드에 동적으로 추가할 수 있다.

메시징 구현에서 살펴보았듯이 Objective-C의 메소드는 단순히 2개의 핵심 인자(self_cmd)를 받는 C함수이다. 이러한 형태로 함수를 정의해두면, class_addMethod 함수를 사용하여 이 함수를 인스턴스나 클래스 메소드로 추가할 수 있다.(이 둘의 구분은 class_addMethod 함수가 위의 두 메소드 중 어디에서 호출되었느냐에 따라 달라진다.) 예를 들어 다음과 같은 함수를 정의했다고 하자.

void dynamicallyMethodIMP(id self, SEL _cmd) {
    /* ...... */
}

이 함수는 다음과 같이 어떤 클래스의 인스턴스 메소드로 동적으로 연결될 수 있다. 이 때 새로 만들어지는 메소드의 이름은 resolveThisMethodDynamically 이다.

@implementation MyClass
+(BOOL) resolveInstanceMethod:(SEL)aSEL {
    if (aSEL == @selector(resolveThisMethodsDynamically)) {
        class_addMethod([self class], aSEL, (IMP) dynamicallyMethodIMP, "v@:");
        return YES;
    }

    return [super resolveInstanceMethod:aSEL];
}

class_addMethod 함수의 원형은 다음과 같다.

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char* types);

여기서 IMP은 실제 셀렉터를 통해 호출해야 할 함수의 포인터를 가리킨다. 그 정의는 다음과 같고 의미는 "함수의 구현"이다..

id (*IMP)(id, SEL, ...);

함수의 타입은 types에 인코딩된 타입문자열로 들어간다. 여기서 실제 구현함수인 dynamicallyMethodIMP는 id 형과 SEL 형을 인자로 받으며, 리턴값은 없다.(void) 따라서 이 함수의 타입을 인코딩하면 "v@:" 이고 이는 void-id-SEL 의 의미가 된다.

메소드를 포워딩하는 것과 동적 메소드 변경은 대략 대조를 이룬다. 클래스는 포워딩 매커니즘이 끼어들기 이전에 메소드를 동적으로 변경할 수 있는 기회를 가진다. 만약 respondsToSelector:instancesRespondsToSelector:가 호출되면, 동적 메소드 변경자가 해당 셀렉터에 대해 IMP를 제공할 기회를 가지게 된다. resolveInstanceMethod:를 구현했는데, 특정한 셀렉터에 대해서는 포워딩하고 싶다면 해당 셀렉터에 대해서 NO를 리턴하도록 하면 된다.

동적 로딩

Objective-C 프로그램은 실행중에 새로운 클래스와 새로운 카테고리를 링크할 수 있다. 새 코드는 프로그램과 상호작용할 수 있고, 맨 처음부터 있었던 것들과 동등하게 취급된다. 동적인 로딩으로 많은 것들을 할 수 있다. 예를 들어 시스템 환경 설정 앱의 많은 모듈들은 모두 동적으로 로딩된다.

코코아 환경에서 동적 로딩은 애플리케이션의 커스터마이징을 위해 흔히 사용된다. 다른 누군가가 당신이 작성한 앱이 실행되는 중간에 로딩될 수 있는 모듈을 작성할 수 있다. 이는 인터페이스 빌더가 커스텀 팔레트를 로딩하는 것이나, OSX의 환경 설정앱이 커스텀 모듈을 읽어들이는 것과 비슷한다. 로드가능한 모듈은 애플리케이션이 할 수 있는 일을 확장시킨다. 대신에 이들은 당신이 허용한 방법으로만 프로그램에 기여할 수 있다. 이를 테면 당신은 프레임워크를 제공하고 다른 사람들이 코드를 제공하는 것이다.

Mach-O 파일들에 있는 Objective-C 모듈을 동적으로 로딩하는 런타임 함수가 있지만, 코코아의 NSBundle 클래스는 이러한 동적 로딩에 대해 훨씬 더 편리한 사용성을 제공한다.

예제