Objective-Cのランタイムプログラミング(Runtime Programming)


1.バージョンとプラットフォーム
     
Runtime Systemは、Objective-Cにとってオペレーティングシステム、またはObjective-Cコードが既定の言語特性に従って走ることができるサポートプラットフォームのようなものです.C/C++に対して、Objective-Cはできるだけいくつかの動作を実行時に延期して実行します.つまり、できるだけ動的に仕事をします.したがって、コンパイラだけでなく、コンパイル後のコードを実行するためのランタイム環境も必要です.Runtime SystemはLegacyとModernの2つのバージョンに分かれていますが、一般的には現在Modernバージョンを使用しています.ModernバージョンのRuntime Systemには、親クラスのメンバー変数のレイアウトが変更された場合、サブクラスを再コンパイルする必要がないという「non-fragile」という顕著な特徴があります.また、宣言されたプロパティの合成操作(@propertyおよび@synthesis)もサポートされています.
次に、NSObjectクラス、Objective-CプログラムがRuntime Systemとどのように対話するか、実行時にクラスを動的にロードし、他のオブジェクトにメッセージを送信するか、実行時にオブジェクト情報を取得する方法について説明します.
2.Runtime Systemとのインタラクション
Objective-CプログラムとRuntime Systemは3つの異なる階層で相互作用する:Objective-Cソースコードを通過する;NSObjectによって定義された関数.およびruntime functionsを直接呼び出すことによって.
通常、Runtime Systemはバックグラウンドで動作します.Objective-Cコードを作成し、コンパイルする必要があります.コンパイラは、言語の動的特性を実現するために、対応するデータ構造と関数呼び出しを作成します.これらのデータ構造には、クラス、Category定義、およびProtocol宣言で見つけられる情報(メンバー変数テンプレート、selectors、およびソースコードから抽出された他の情報など)が保存されます.
Runtime Systemは、/usr/include/objcにある動的共有ライブラリで、一連の関数とデータ構造からなる共通のインタフェースを備えています.開発者は純粋なCを使っていくつかの関数を呼び出してコンパイラのすることをすることができて、あるいはRuntime Systemを拡張して、開発環境のためにいくつかのツールを作ることができます.一般的にObjective-Cの作成にはこれらの内容を理解する必要はありませんが、役に立つ場合があります.すべての関数はObjective-C Runtime Referenceにドキュメント化された情報があります.
CocoaのほとんどのオブジェクトはNSObjectのサブクラス(NSProxyは例外)であり,NSObjectのメソッドを継承している.したがって、この継承システムでは、サブクラスは、必要に応じてNSObjectによって定義されたいくつかの関数を再実装し、descriptionメソッドなどの多様性と動的性を実現することができる(Pythonの先頭の3引用符のような独自の文字列を記述することを返す).
いくつかのNSObject定義の方法は、単にRuntime Systemに問い合わせて情報を取得することによって、クラスタイプを決定するために使用されるisKindOfClass、継承システムにおけるオブジェクトの位置を決定するisMemberOfClass、特定のメッセージを受信できるかどうかを判断するrespondsToSelectorのように、オブジェクトを自己省察(introspection)することができるようにするだけである.オブジェクトがプロトコルのconformsToProtocol:、メソッド実装アドレスを提供するmethodForSelector:.これらの方法は、1つのオブジェクトを自己省察することができる(introspect about itself).
最も主要なRuntime関数は、ソースコードのメッセージ式によって励起されるメッセージを送信するために使用されます.メッセージの送信は、Objective-Cプログラムで最も頻繁に行われる式であり、最終的にはobjc_に変換されます.msgSend関数呼び出し.たとえば、メッセージ式[receiver message]がobjc_に変換されます.msgSend(receiver,selector)、パラメータがあればobjc_msgSend(receiver, selector, arg1, arg2, …).
メッセージは実行時にのみ関数実装にバインドされます.まずobjc_msgSendはreceiverでselectorに対応する関数実装を検索する.次に関数プロシージャを呼び出し、receiving object(thisポインタ)とパラメータを渡します.最後に、関数の戻り値を返します.
メッセージを送信する鍵は、コンパイラがクラスとオブジェクトのために作成した構造であり、superclassへのポインタとクラスのdispatch tableの2つの主要要素を含み、1つはselectorと対応する関数エントリアドレスを関連付けるクラスのdispatch tableである.
オブジェクトが作成されると、メモリレイアウトの最初の要素はクラス構造へのポインタ、isaです.isaポインタにより、1つのオブジェクトがクラス構造にアクセスし、継承されたクラス構造にアクセスできます.例示的な図は、ここを参照してください.
オブジェクトにメッセージを送信するとobjc_msgSendは、isaポインタを介してクラスのdispatch tableでselectorに対応する関数エントリアドレスを検索し、見つからない場合は、NSObjectクラスまでclass hierarchy(クラスの継承システム)に沿って検索します.これが実行時に関数を選択して実現され,OOPの行話では動的バインドである.
メッセージの送信速度を速めるため、Runtime Systemは各クラスに対してselectorと対応する関数エントリアドレスのマッピングをキャッシュするcacheを作成した.
objc_msgSendが対応する関数実装を見つけた場合、関数パラメータの伝達に加えて、receiving objectとselectorの2つの非表示パラメータも伝達されます.非表示パラメータと呼ばれるのは、ソースコードに宣言が表示されていないためですが、selfと_cmdがアクセスします.
メッセージがオブジェクトに何度も送信される場合は、methodForSelector:を使用して最適化できます.たとえば、次のコードがあります.
void (*setter)(id, SEL, BOOL);  
int i;  
  
setter = (void (*)(id, SEL, BOOL))[target  
     methodForSelector:@selector(setFilled:)];  
for ( i = 0; i < 1000, i++ )   
     setter(targetList[i], @selector(setFilled:), YES);  

ここで、methodForSelector:Objective-C自体の言語特性ではなく、Cocoa Runtime Systemによって提供されています.ここでは、戻り値とパラメータを含む変換中の関数タイプの正確性に注意し、ここで最初の2つのパラメータはidとSELとして宣言される必要があります.
3.方法の動的決議
Objective-Cの@dynamicインジケータなどの方法の実装を動的に提供したい場合があります.これは、コンパイラが属性に対応する方法が動的に提供されていることを示します.resolveInstanceMethod:とresolveClassMethod:を使用して、それぞれオブジェクトメソッドとクラスメソッドの動的実装を提供できます.
1つのObjective‐C法は本質的に少なくとも2つのパラメータ(selfと_cmd)を持つC関数であり,class_addMethodはクラスにメソッドを追加します.たとえば、次の関数です.
void dynamicMethodIMP(id self, SEL _cmd) {  
     // implementation ….  
}  

resolveInstanceMethod:resolveThisMethodDynamicallyなどのメソッドに追加できます.
@implementation MyClass  
+ (BOOL)resolveInstanceMethod:(SEL)aSEL  
{  
     if (aSEL == @selector(resolveThisMethodDynamically)) {  
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");  
          return YES;  
     }  
     return [super resolveInstanceMethod:aSEL];  
}  
@end  

動的決議とメッセージの送信は衝突せず、メッセージメカニズムが機能する前に、クラスはメソッドを動的に決議する機会があります.respondsToSelector:またはinstancesRespondToSelector:がアクティブ化されると、dynamic method resolverは、このselectorに実装を提供する機会を優先します.resolveInstanceMethod:が実現された場合、動的に決定したくなくてメッセージ転送メカニズムに従わせたいselectorsに対してNOを返せばよい.
Objective-Cプログラムは、実行時に新しいクラスとcategoryをリンクできます.ダイナミックロードは、System Preferencesのさまざまなモジュールがダイナミックロードされているなど、さまざまなことをするために使用できます.Objective-Cモジュール(objc/objc-load.hのobjc_loadModules)を動的にロードできるランタイム関数がありますが、CocoaのNSBundleクラスはより便利な動的ロードインタフェースを提供します.
4.メッセージ転送
1つのオブジェクトに処理しないメッセージを送信するのはエラーですが、エラーを報告する前にRuntime Systemはオブジェクトを受信する2回目の機会を与えてメッセージを処理します.この場合、Runtime Systemはオブジェクトにメッセージを送信します.forwardInvocation:このメッセージは、元のメッセージと対応するパラメータをパッケージしたNSInvocationオブジェクトをパラメータとして1つしか持っていません.
forwardInvocation:メソッド(NSObjectに継承)を実装することで、応答しないメッセージにデフォルトの処理方法を与えることができます.メソッド名のように、通常の処理は、メッセージを別のオブジェクトに転送することです.
- (void)forwardInvocation:(NSInvocation *)anInvocation  
{  
     if ([someOtherObject respondsToSelector:[anInvocation selector]])  
          [anInvocation invokeWithTarget:someOtherObject];  
     else  
          [super forwardInvocation:anInvocation];  
} 

認識されていないメッセージ(dispatch tableでは見つからない)についてforwardInvocation:中継局のように、配達を継続したいか、処理を停止したいかは、開発者が決定します.
5.タイプコード
Runtime Systemをサポートするために、コンパイラは戻り値タイプ、パラメータタイプをエンコードし、対応するコンパイラインジケータは@encodeです.
例えば、void符号化はv、char符号化はc、オブジェクト符号化は@、クラス符号化は#、セレクタ符号化は:、適合タイプは基本タイプからなり、例えば
typedef struct example {  
     id     anObject;  
     char *aString;  
     int anInt;  
} Example;

コードは{example=@*i}です.
6.属性宣言
コンパイラがプロパティ宣言に遭遇すると、対応するクラス、category、プロトコルに関連付けられた記述可能なメタデータ(metadata)が生成されます.クラスまたはプロトコルでこれらのmetadataを名前で検索できる関数があります.これらの関数により、符号化された属性タイプ(文字列)、属性をコピーするattributeリスト(C文字列配列)を得ることができます.したがって、各クラスとプロトコルのプロパティリストを取得できます.
タイプ符号化と同様に、readonly符号化R、copy符号化C、retain符号化&など、属性タイプにも対応する符号化スキームがある.
propertyを介してgetAttributes関数は、Tで始まり、@encode typeとカンマに続き、Vと変数名で終わる後の文字列を後に符号化することができます.例:
@property char charDefault;  

説明:Tc,VcharDefault
@property(retain)ididRetain; 

説明:T@,&,VidRetain
Property構造体は、属性記述子を指す不透明なハンドルを定義します.typedef struct objc_property *Property;.
class_経由copyPropertyListとprotocol_copyPropertyList関数は、対応する属性配列を取得します.
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)  
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

propertyを介してgetAttributes関数は、属性の@encode type stringを取得します.
const char *property_getAttributes(objc_property_t property)
上記の関数は、サンプルコードに結合されています.
@interface Lender : NSObject {  
     float alone;  
}  
@property float alone;  
@end  
  
id LenderClass = objc_getClass("Lender");  
unsigned int outCount, i;  
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);  
for (i = 0; i < outCount; i++) {  
     objc_property_t property = properties[i];  
     fprintf(stdout, "%s %s
", property_getName(property), property_getAttributes(property)); }

参考資料:Objective-C Runtime Programming Guide