カテゴリによる既存クラスの拡張

NSTextViewはテキストを表示するビューですが,そこには「表示されているすべてのテキストの範囲を知る」というよく使われるメソッドが定義されていません.NSTextViewにはこの範囲を得るための基本的なメソッドはもちろん定義されていますから,それらを組み合わせればいいのですが,よく使うものなので「一発で」知りたいと思うのも当然でしょう.

ではNSTextViewにこのようなメソッド(ここでは-allRangeとしましょう)を追加するにはどうすればよいでしょう?

普通クラスにメソッドを追加する場合はサブクラスを作ります.しかし-allRangeのようなユーティリティ・メソッドを追加しただけで別のクラスにするのも腑に落ちません.

そこでObjective-Cではカテゴリを使います.

NSTextViewAdditions.h

普通は拡張するクラスの名前に「Additions」を追加したファイル名を使うことが多いようです.

#import <appkit/appkit.h>

@interface NSTextView (MetabolicsAddition)

- (NSRange)allRange;
- (NSRange)lastRange;

@end

ここではMetabolicsAdditionという名前のカテゴリをNSTextViewに追加し,-allRange-lastRangeという二つのメソッドを定義しました.

NSTextViewAdditions.m

#import "NSTextViewAddtions.h"

@implementation NSTextView (MetabolicsAddition)

- (NSRange)allRange {
	return NSMakeRange(0, [[textView textStorage] length]);
}

- (NSRange)lastRange{
	int last = [[textView textStorage] length];
	return NSMakeRange(last, last);
}

@end

NSTextViewAdditions.hで宣言した二つのメソッドの実装を与えました.

これでこれらのファイルをProjectBuilderに加えて一緒にビルドしてやれば,あたかもNSTextViewが最初から-allRange-lastRangeの二つのメソッドをもっていたかのように,これらを使うことができます.

カテゴリの制限

なんて素晴しい! Objective-Cのパワーをわかっていただけるかと思います.

ところで,カテゴリには制限もあります.それは

の二つです.

普通に考えられるObjective-Cの実装では,インスタンス変数の追加をしてしまうとクラスやサブクラスの再コンパイルが必要になってしまいます.また,本来のクラスがもっているインスタンス変数の名前との衝突がありえます.さらにインスタンス変数の追加が必要ということは,オブジェクトの性質が拡張されたことに非常に近いことになってしまいます.

したがってインスタンス変数の追加が必要となるような拡張はサブクラス化を用いて行なってください.

同じクラスに対する複数のカテゴリで同じ名前のメソッドを定義しないようにするには,注意するしかありませんね.

カテゴリのそのほかの使われ方

カテゴリは既存のクラスを拡張する場合だけではなく,最初から使われることもよくあります.

もっともよく使われる例がPrivateカテゴリです.C++やJavaのプライベート関数に相当するメソッドをPrivateというようなカテゴリにまとめ,@interfaceも.hファイルではなく,.mファイルに宣言します.

@interface Foo (Private)
- (void)privateMethod;
@end

@implementation Foo (Private)
- (void)privateMethod {
	//......
}
@end

これによって,外からはそのメソッドの存在は隠され,ドキュメンテーション上もメソッドの役割ははっきりします.ただし,C++やJavaの場合と違い,このメソッドを外から呼ぼうと思えば呼べてしまうことに注意してください.

それ以外にもカテゴリはメソッドのグループ化にもよく使われます.

非常に多くのメソッドをもつ高機能なクラスでは,メソッドを列挙しても多すぎたり,ファイルが長くなりすぎ,ドキュメンテーション上問題になる場合があります.このような場合にメソッドを機能ごとにグループ化することがよく行なわれます.

例としては...などをご覧ください.

そのほかの話題

カテゴリと同じようなことはクラスのPosingを使って行なうこともできます.Posingはサブクラスをその親クラスのように見せかける,動的な機能です.