Dynamic Property
Pythonばっかいじってたせいで、dir(obj) とかやってしまいそうになりますが、ObjCではうまくいきません(当たりまえですが)。
でも、CoreData の Entity は上手く動いてます。そんなわけでできるはずなので探した結果、下記のようなコードになりましたとさ。
クラスのインスタンス変数を動的に取ってくる事が出来れば、一番いいのですが、さすがにそこまで書くのは今は面倒なので、プロパティ一覧は別に用意し、内部で読み込んでインスタンスメソッドを追加して使うという形にします。
プロパティ一覧は、「「KGDyProperty」を継承したクラス名.conf」というファイルを用意し、改行区切りでプロパティを書き込みます。そして、コンパイル時にそのファイルをリソースフォルダにコピーするようにしておきます。
KGDyProperty.h
// // KGDyProperty.h // kogetsu // // Created by at_yasu on 10/11/26. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import <Foundation/Foundation.h> // ベースクラス @interface KGDyProperty : NSObject { } @end
KGDyProperty.m
// // KGDyProperty.m // kogetsu // // Created by at_yasu on 10/11/26. // Copyright 2010 __MyCompanyName__. All rights reserved. // // via http://efreedom.com/Question/1-3560364/Writing-Dynamic-Properties-Cocoa #import <objc/objc-runtime.h> #import "KGDyProperty.h" // fn = obj.prop; の時に呼び出される。 id accessorGetter(id self, SEL _cmd) { NSString *method = NSStringFromSelector(_cmd); // Return the value of whatever key based on the method name NSLog(@"%@ %@", self, NSStringFromSelector(_cmd)); return nil; } // obj.prop = set の時に呼び出される。anID がプロパティ名になる void accessorSetter(id self, SEL _cmd, NSObject* newValue) { NSString *method = NSStringFromSelector(_cmd); // remove set NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""]; // Set value of the key anID to newValue NSLog(@"%@ %@ id:%@", self, NSStringFromSelector(_cmd), anID); } @implementation KGDyProperty - (id) init { if ((self = [super init])) {} return self; } + (id) allocWithZone:(NSZone *)zone { if ((self = [super allocWithZone:zone])) { NSAutoreleasePool* rootPool = [[NSAutoreleasePool alloc] init]; const char* name = class_getName([self class]); NSString* classname = nil; classname = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding]; NSString* path = [[NSBundle mainBundle] pathForResource:classname ofType:@"conf"]; if (path) { NSArray* prop = nil; NSString* fileContents = nil; NSError* err = nil; fileContents = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err]; if ([fileContents length]) { prop = [fileContents componentsSeparatedByString:@"\n"]; } else { NSLog(@"cannot load to %@: %@",path, err); } [fileContents release]; // Add Property methods. for (NSString* name in prop) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; if ([name length]) { NSString* _name = [name copy]; SEL getter = NSSelectorFromString(_name); [[self class] resolveInstanceMethod:getter]; [_name release]; _name = [[NSString alloc] initWithFormat:@"set%@", [name capitalizedString], nil]; SEL setter = NSSelectorFromString(_name); [[self class] resolveInstanceMethod:setter]; [_name release]; } [pool release]; } } else { NSLog(@"File<%@> is not found.", path); } [classname release]; [rootPool release]; } return self; } + (BOOL)resolveInstanceMethod:(SEL)aSEL { NSString *method = NSStringFromSelector(aSEL); if ([method hasPrefix:@"set"]) { class_addMethod([self class], aSEL, (IMP) accessorSetter, "v@:@"); return YES; } else { class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @end
以下、任意の実装。
KGTwEntry.h
// // KGTwEntry.h // kogetsu // // Created by at_yasu on 10/11/25. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import <Foundation/Foundation.h> #import "KGDyProperty.h" @interface KGTwEntry: KGDyProperty { @private NSNumber* favorited; NSDate* created_at; NSNumber* truncated; } @property (nonatomic, assign) NSNumber* favorited; @property (nonatomic, retain) NSDate* created_at; @property (nonatomic, assign) NSNumber* truncated; @end
KGTwEntry.m
// // KGTwEntry.m // kogetsu // // Created by at_yasu on 10/11/25. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import "KGTwEntry.h" @implementation KGTwEntry @dynamic favorited; @dynamic created_at; @dynamic truncated; @end
KGTwEntry.conf
favorited created_at truncated
そんな感じで、後はこんな感じのコードを書いて、
... KGTwEntry* ent = [[KGTwEntry alloc] init]; ent.favorited = [NSNumber numberWithBool:YES]; ent.created_at = [NSDate date]; ent.truncated = [NSNumber numberWithBool:NO]; [ent release]; ...
実行すれば、
2010-11-26 02:05:30.746 testprop[1739:207] *nil description* setFavorited: id:favorited 2010-11-26 02:05:30.746 testprop[1739:207] *nil description* setCreated_at: id:created_at 2010-11-26 02:05:30.748 testprop[1739:207] *nil description* setTruncated: id:truncated
こんな表示が来ますよっと。