@dynamic 사용법 소개
Objective-C 2.0은 컴파일러가 자동으로 setter 및 getter 메소드를 생성할 수 있도록 하는 속성(@property)을 제공합니다. 컴파일러가 이러한 setter 및 getter 메서드를 자체적으로 생성하지 않도록 하려면 @dynamic을 사용하세요. 간단한 예를 들면 다음과 같습니다.
#import <Foundation/Foundation.h> @interface Person : NSObject @property (copy) NSString *name; @end @implementation Person // @dynamic tells compiler don't generate setter and getter automatically @dynamic name; @end int main(int argc, const charchar * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Person *a = [[Person alloc] init]; a.name = @"Hello"; // will crash here NSLog(@"%@", a.name); [a release]; [pool drain]; return 0; } // main
프로그램을 실행할 때 Xcode는 "-[PersonsetName:]: 인식할 수 없는 선택기가 인스턴스 0x1001149d0으로 전송되었습니다"라는 오류를 보고합니다. @dynamic을 주석 처리하면 모든 것이 정상입니다.
여기에서는 @dynamic을 사용하기 때문에 setter 및 getter 메소드를 직접 제공해야 합니다. 일반적으로 두 가지 방법이 있습니다. 1) 직접 setter 및 getter 메서드를 제공하고, 컴파일러에서 자동으로 생성된 setter 및 getter 메서드를 수동으로 다시 작성합니다. 2) 동적 메서드 확인(DynamicMethod Resolution), 런타임 C 함수에서 해당 setter 및 getter 구현을 제공합니다. .
첫 번째 방법의 경우 @dynamic은 @synthesize처럼 구현 파일(.m)에 인스턴스 변수를 제공할 수 없기 때문에 클래스에 인스턴스 변수를 명시적으로 제공해야 합니다.
#import <Foundation/Foundation.h> @interface Person : NSObject { // must provide a ivar for our setter and getter NSString *_name; } @property (copy) NSString *name; @end @implementation Person // @dynamic tells compiler don't generate setter and getter automatically @dynamic name; // We provide setter and getter here - (void) setName:(NSString *)name { if (_name != name) { [_name release]; _name = [name copy]; } } - (NSString *) name { return _name; } @end // Person int main(int argc, const charchar * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Person *a = [[Person alloc] init]; a.name = @"Hello"; // Ok, use our setter a.name = @"Hello, world"; NSLog(@"%@", a.name); // Ok, use our getter [a release]; [pool drain]; return 0; } // main
두 번째 방법의 경우 NSObject에서 제공하는solveInstanceMethod: 메서드를 사용하여 런타임 시 setter 및 getter 구현에 해당하는 C 함수를 결정합니다. 인스턴스 변수는 C 함수에서 직접 사용할 수 없습니다. ObjC 객체 자체는 C에서 구조체로 변환되어야 합니다. 따라서 인스턴스 변수도 Person 클래스에서 명시적으로 선언되어야 하며 액세스 수준은 @public입니다. 인스턴스 변수를 숨기고 In 확장(extension)
#import <Foundation/Foundation.h> #import <objc/objc-runtime.h> // for class_addMethod() // ------------------------------------------------------ // A .h file @interface Person : NSObject @property (copy) NSString *name; - (void) hello; @end // ------------------------------------------------------ // A .m file // Use extension to override the access level of _name ivar @interface Person () { @public NSString *_name; } @end @implementation Person // @dynamic implies compiler to look for setName: and name method in runtime @dynamic name; // Only resolve unrecognized methods, and only load methods dynamically once + (BOOL) resolveInstanceMethod:(SEL)sel { // Capture setName: and name method if (sel == @selector(setName:)) { class_addMethod([self class], sel, (IMP)setName, "v@:@"); return YES; } else if (sel == @selector(name)) { class_addMethod([self class], sel, (IMP)getName, "@@:"); return YES; } return [super resolveInstanceMethod:sel]; } void setName(id self, SEL _cmd, NSString* name) { // Implement @property (copy) if (((Person *)self)->_name != name) { [((Person *)self)->_name release]; ((Person *)self)->_name = [name copy]; } } NSString* getName(id self, SEL _cmd) { return ((Person *)self)->_name; } - (void) hello { NSLog(@"Hello, world"); } @end // Person int main(int argc, const charchar * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Person *a = [[Person alloc] init]; [a hello]; // never call resolveInstanceMethod a.name = @"hello1"; NSLog(@"%@", a.name); a.name = @"hello2"; NSLog(@"%@", a.name); [a release]; [pool drain]; return 0; } // main
에 선언을 넣습니다. 위 내용을 요약하면 @dynamic의 기능은 컴파일러가 @property에 대한 setter 및 getter 메서드를 생성하는 것을 금지하는 것입니다. setter 및 getter 메소드를 구현하는 두 가지 방법: 1) setter 및 getter 메소드를 직접 제공합니다. 2) 메소드 동적 해결(DynamicMethod Resolution).