libextobjc の @ keypath でObjective-Cのプロパティ名を文字列化する

まえから、型チェックをした上でプロパティ名を文字列化したいと思っていました。

@interface MyClass
@property (nonatomic, strong) NSString * myPropertyName;
@end
@implementation MyClass
-(void) myFunc {
self.myPropertyName = @"abc";
NSString * property = NSStringFromSelector(@selector(myPropertyName));
// myPropertyName が出力される
NSLog(@"%@", property);

// abc が出力される
NSLog(@"%@",[self valueForKey:property]);
}
@end

これだと、SEL型から名前をえることが出来るのですが、myPropertyNameというプロパティが、MyClassのプロパティという保証がないので、困ることがあります。valueForKey:で使用した時に間違った方で使用してしまうこともあります。それを解決する方法をまえから考えていたのですが思い浮かばなかったものの、ReactiveCocoa(ReactiveCocoa/ReactiveCocoa )使っていたら、Libextobjc(jspahrsummers/libextobjc )にそれっぽいものがあったので調べてみました。libextobjc は、Objective-Cを便利に使う機能をまとめた関数群で、マクロなどを駆使して欲しい機能が実装されています。libextobjcの、”Compile-time checking of selectors”にはこのように説明されています。

Compile-time checking of selectors to ensure that an object declares a given selector, using EXTSelectorChecking.

セレクタの型チェックをコンパイル時に行うことが出来る、いい感じですね。これで上を書き換えるとこうなります。


@interface MyClass
@property (nonatomic, strong) NSString * myPropertyName;
@end
@implementation MyClass
-(void) myFunc {
self.myPropertyName = @"abc";
NSString * property = @keypath(self.myPropertyName);
// myPropertyName が出力される
NSLog(@"%@", property);

// abc が出力される
NSLog(@"%@",[self valueForKey:property]);
}
@end

分かりやすいですね!ここで、”self.myPropertyName”と書いているので、どのインスタンスの型のプロパティかがコンパイル時にチェック出来ます。RestKitやMantleなどのO/Rマッピング機能を使うために、文字列型でプロパティ名が欲しいことが多いのでとても役立ちます。

どうやっているのかを見て見たところこんな感じでした。
/**
* \@keypath allows compile-time verification of key paths. Given a real object
* receiver and key path:
*
* @code

NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// => @"lowercaseString.UTF8String"

NSString *versionPath = @keypath(NSObject, version);
// => @"version"

NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
// => @"lowercaseString"

* @endcode
*
* ... the macro returns an \c NSString containing all but the first path
* component or argument (e.g., @"lowercaseString.UTF8String", @"version").
*
* In addition to simply creating a key path, this macro ensures that the key
* path is valid at compile-time (causing a syntax error if not), and supports
* refactoring, such that changing the name of the property will also update any
* uses of \@keypath.
*/
#define keypath(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))

#define keypath1(PATH) \
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

分かりにくいですが、keypath1のところだけを見てみると、

#define keypath1(PATH) \
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

こんな感じで、左の、”(NO && ((void)PATH, NO))”でコンパイルチェックだけを通して、実際にはNO &&の後なので使用されないようにしています。
後半の右側で、パラメータを#defineマクロの機能で、文字列化して(# PATH)、その”.”の右側をとったC文字列を作成して、”myPropertyName”となるのですが、keypath( の前に@を付けているので、Objective-C文字列になるという訳ですね。

言葉で説明すると分かりにくいですが、使うと便利と思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください