Objective-Cで、サブクラスだけで使用出来るプロパティを作成する

Objective-Cでクラスを作る場合は、他のクラスからアクセスさせたくないプロパティはクラスエクステンションにして、自クラスだけでアクセス出来るようにします。こんな感じです。

PCEBaseClass.h
PCEBaseClass.h

モジュールはこんな感じです。
PCEBaseClass.m
PCEBaseClass.m

こうすることによって、notesというプロパティは、参照するクラスからは変更されたくないので、ヘッダーにはreadonly,クラスエクステンションでは、readwriteで定義することによって、クラス内で変更可能です。
booksというプロパティは、クラス内ではNSMutableArrayとして振る舞いたいけれども、対外的には変更してほしくないので、NSArrayとして返しています。booksと別のbooksInternalという内部用プロパティをNSMutableArrayで定義して、内部ではbooksInternalにアクセスすることによって、オブジェクトの追加が出来るようにしています。

このクラスを継承したクラスを作った場合は、対外的には同じくbooks, notesを触られたくないですが、継承したクラス内では触りたい訳です。

PCEExtendedClass.h
PCEExtendedClass.h

モジュールでbooks, notesを触ろうとすると当然ですが、エラーになります。
PCEExtendedClass.m
PCEExtendedClass.m

これを解決するために、クラスエクステンションを別ファイルに出してあげると解決出来ました。
Class Extentionを作成
Class Extentionを作成

このようにします。
クラスエクステンション作成2
クラスエクステンション作成2

クラスエクステンションの新しいファイルに、プロパティを記述
クラスエクステンションの内容
クラスエクステンションの内容

ベースクラスでは、クラスエクステンションをモジュールでImportします。

ベースクラス
ベースクラス

継承したクラスでもクラスエクステンションをモジュールでImportします。booksに関しては、内部的にmutableのプロパティ booksInteranlをクラスエクステンションで定義しているのでそちらを使用します。

継承したクラス
継承したクラス

これで、他のクラスからはprivateExtentionのヘッダーをインポートしないようにすれば定義が見えないので、コンパイル時点で正しい処理が行われていることを確認出来ます。

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文字列になるという訳ですね。

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