동적 메소드 변형
메소드를 동적으로 추가하기
(도대체 언제인지는 감을 잡기 힘들지만) 객체에 동적으로 기능의 구현을 추가해야 할 경우가 이따금씩 있다. 예를 들자면 @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
클래스는 이러한 동적 로딩에 대해 훨씬 더 편리한 사용성을 제공한다.
예제
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |