Objective-C2.0のランタイムAPIには、class_addMethod()といった関数が用意されており、これを使うとあるクラスに対して実行時にメソッドを追加して、クラスの機能を拡張することができます。
とは言っても、メソッドのみを追加しても、それに関連するインスタンス変数がないのでは、機能の拡張にも限界があります。そこで今回は、実行時にインスタンス変数を追加する技を紹介しましょう。
全てのオブジェクトにはisaと呼ばれるフィールドがあり、これでClassオブジェクトを参照しています。このisaで参照されるものは、極端に言うとClassオブジェクトであれば何でもいいので、実行時に別のものと差し替えることができます。そして、これを利用してインスタンス変数の追加が可能です。
まず、objc_allocateClassPair()とobjc_registerClassPair()関数を使って、新規クラスを作りますが、このときスーパークラスとしてオブジェクトの元のクラスを設定し、extraBytesにも値を設定して余分にメモリを確保します。この余分に確保したメモリが追加する変数のエリアとなります。
このクラスを差し替えれば、変数エリアの追加が完了します。
#import <objc/runtime.h>
id object; // 変数を追加するオブジェクト
Class insert;
Class person = object_getClass(object);
insert = objc_allocateClassPair(person, "Insert", sizeof(int));
objc_registerClassPair(insert);
object_setClass(object, insert);
追加した変数エリアには、object_getIndexedIvars()でアクセスできます。
例えば、単純なアクセッサメソッドなら、以下のように書きます。
int value(id self, SEL _cmd)
{
Class insert = object_getClass(self);
int* value = (int*)object_getIndexedIvars(insert);
return *value;
}
void setValue(id self, SEL _cmd, int newValue)
{
Class insert = object_getClass(self);
int* value = (int*)object_getIndexedIvars(insert);
*value = newValue;
}この技は、KeyValueObserving(KVO)で使われているもので、あるオブジェクトのaddObserver:forKeyPath:options:context:メソッドを呼ぶと、上記のような方法で、そのオブジェクトの機能がダイナミックに拡張されます。もし、この方法を使って拡張したオブジェクトにさらにKVOを使ったり、既にKVOによって拡張されたオブジェクトをさらに拡張しようとすると不具合を起こすので、その辺はうまく工夫する必要があります。