介紹@dynamic的用法
Objective-C 2.0提供了屬性(@property),可以讓編譯器自動產生setter和getter方法。如果不想編譯器自作主張產生這些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:]: unrecognized selector sent to instance 0x1001149d0」。如果將@dynamic註解掉,則一切Ok。
這裡由於使用@dynamic,我們需要自己提供setter和getter方法。一般有兩種方法:1)自己提供setter和getter方法,將編譯器自動產生的setter和getter方法手動再寫一遍;2)動態方法決議(DynamicMethod Resolution),在運行時提供setter和getter對應實現的C函數。
對於第一種方法,需要在類別中明確提供實例變量,因為@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
第二種方法,在執行時決定setter和getter對應實現的C函數,使用了NSObject提供的resolveInstanceMethod:方法。在C函數中不能直接使用實例變量,需要將ObjC物件self轉成C中的結構體,因此在Person類別同樣需要明確聲明實例變數而且存取層級是@public,為了隱藏該實例變量,將聲明放在擴充(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)。