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

[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 클래스는 이러한 동적 로딩에 대해 훨씬 더 편리한 사용성을 제공한다.

예제

#import <Foundation/Foundation.h>
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"This Method(%@) is dynamically resolved.", NSStringFromSelector(_cmd));
}
@interface MyObject : NSObject
{
int i;
}
@end
@implementation MyObject
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
int main(int argc, char const *argv[])
{
@autoreleasepool{
MyObject *m = [[MyObject alloc] init];
[m resolveThisMethodDynamically];
[m release];
}
return 0;
}

“[Objective-C] 클래스,인스턴스 메소드를 동적으로 추가하기”의 13개의 댓글

  1. 염치 불구하고 질문 하나더 있습니다.
    데모 어디에서도 @dynamic propertyName; 와같은 문법을 사용하는 곳이 없는데요
    동적 메서드 바인딩과 @dynamic propertyName; 문법과 어떤 연관관계가 있는지 궁금합니다.
    어찌 보면 @dynamic propertyName; 문법과는 상관없이 class_addMethod 함수 활용한 동적 메서드 바인딩 같아서요

    1. 자답합니다.
      @interface
      @property void resolveThisMethodDynamically;
      @end
      @implementation
      @dynamic resolveThisMethodDynamically;
      @end
      상기 문구를 각 위치에 추가해주면 xcode상에서 ARC를 끄지 않아도 정상적으로 빌드가 가능합니다.
      감사합니다.

      1. @dynamic 지시어를 사용하는 예는 코어데이터외에는 저도 거의 못 본 것 같습니다. 이 지시어 자체의 의미가 “이 메소드는 동적으로 리졸빙하니 별도로 자동 작성하지 않는다” 정도로 이해하는데요. (@synthesize의 경우에는 컴파일러가 프로퍼티 접근자 메소드를 자동으로 작성합니다) 이 지시어를 사용하면 Xcode도 해당 접근자에 대해서는 실행시간에 결정되는 것으로 간주, 셀렉터를 찾을 수 없어도 무시하는 듯 하네요.
        감사합니다.

    2. sample로 제공된 소스 컴파일 되나요? 제가 소스 넣고 컴파일 해보니 resolveThisMethodDynamically 메서드가 선언되지 않았다고 하는데 추가적으로 resolveThisMethodDynamically을 선언해줘야 하나요? 선언해줘도 resolveInstanceMethod가 호출안되는데요?!

      1. 경고는 뜨는데요 (클래스메소드를 인스턴스에서 호출해서), 컴파일은 됩니다.
        첨부하신 그림을 보니, output 콘솔에 로그 메시지가 찍힌듯 한데요?
        음 그런데 앞뒤로 NSLog 찍으신 부분은 안나오네요;;; 읭?

          1. xcode에서는 semantic issue 오류 나는데, 저도 console에서 빌드 해봐야겠네요
            빠른 답변 감사합니다.

            1. console은 잘되네요;; 세미나 해야하는데 이를 어찌 설명해야 할까요;; ㅋ

            2. Log가 찍인 이유는 제가 resolveThisMethodDynamically를 인스턴스 메서드로 추가해보고 혹시 제가 추가한 resolveThisMethodDynamically 메서드가 불렸을 때 찍힌 Log입니다.
              지금 스샷에서는 빨간색 느낌표 Error로 인해 빌드 자체가 안되어서요 ㅜㅜ
              이글이 developer 문서를 단지 번역해둔 거라는건 알겠는데 어떻게 하면 동적으로 메서드를 추가해볼수 있을까요?

              1. 오류패널에서 에러는 ARC관련 한 내용이네요. 해당 파일에 대해 ARC를 끄고 컴파일 해보시면 왠지 될 거 같은 느낌이 듭니다.

                1. 느낌이 맞으세요 ARC 끄고 하니 xcode에서도 빌드 됩니다.
                  성실한 답변 감사합니다.

                  1. 참고로, 따로 선언을 안하고 쓰려는 게 이 예제의 목적입니다. `resolveInstanceMethod`는 NSObject의 클래스 메소드이구요. MyObject에 `resolveThisMethodDynamically`가 호출되면, 해당 셀렉터에 대응되는 인스턴스, 클래스 메소드가 없기 때문에 super의 디스패치 테이블을 뒤져볼텐데, 역시 해당 셀렉터는 없을 겁니다. 그럼 “알 수 없는 셀렉터”에 대한 예외를 일으키는 과정에서 NSObject에 미리 정의된 몇 몇 함수가 호출됩니다. `resolveInstanceMethod`는 그 중 한 과정이구요. 아마 제가 알기로는 최종적으로는 메시지를 포워딩하는 함수가 최종적으로 호출되는데, NSObject의 이 메소드의 디폴트 구현은 “알 수 없는 셀렉터” 예외를 일으키는 동작입니다.

댓글 남기기