C-Schnicks用のObjective-Cランタイム。 パート3

画像



みなさんこんにちは。 今日は、Objective-C Runtimeの内部構造、特にC言語レベルでの実装について説明します。



過去の記事では、セレクターと、オブジェクトおよびクラスにメッセージを送信するメカニズムについて詳しく説明しました。 今日は、メッセージで終わり、Objective-C言語の組み込み機能のいくつかの動作原理についてお話したいと思います。



まだ馴染みのない人のために、 最初最初の部分を最初に読むことをお勧めします。興味がある人や興味がある人は猫をお願いします。



objc_msgSend()についてもう少し



ドキュメントは、4つのobjc_msgSend()関数が存在するという信じられないほどの嘘です。







このドキュメントの一部を翻訳します:



メソッドの呼び出しに関しては、コンパイラーは、宛先、戻り値、および引数リストに応じて、メソッド呼び出しを処理するための関数の1つ(上記、 著者の注意 )の呼び出しを生成できます。




実際、これらの関数のリストははるかに広く、その数はプラットフォームごとに異なります。 たとえば、i386の場合、このリストは次のようになります。



.long _objc_msgSend .long _objc_msgSend_fpret .long _objc_msgSend_stret .long _objc_msgSendSuper .long _objc_msgSendSuper_stret
      
      







、しかしarm64の場合は次のようになります。



 .quad _objc_msgSend .quad _objc_msgSendSuper .quad _objc_msgSendSuper2
      
      







接尾辞「stret」を持つ関数は、複雑な型(構造)の変数を返すメソッドに使用され、そのような接尾辞のない関数は単純型の値を返します。 次のコードを例として使用できます。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> struct TestStruct { long firstValue; long secondValue; long thirdValue; }; @interface TestClass : NSObject @end @implementation TestClass + (struct TestStruct)someMethod { struct TestStruct * s = malloc(sizeof(struct TestStruct)); return *s; // returns a whole struct } + (struct TestStruct *)anotherMethod { struct TestStruct * s = malloc(sizeof(struct TestStruct)); return s; // returns just a pointer } @end int main(int argc, const char * argv[]) { // objc_msgSend_stret() struct TestStruct s = [TestClass someMethod]; // objc_msgSend() struct TestStruct * ps = [TestClass anotherMethod]; return 0; }
      
      







親クラスのメソッドを呼び出すときに、接尾辞「Super」が付いた関数が使用されます。次に例を示します。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject @end @implementation TestClass - (NSString *)description { // objc_msgSendSuper2() return [super description]; } @end int main(int argc, const char * argv[]) { TestClass * myObj = [[TestClass alloc] init]; [myObj description]; return 0; }
      
      







最後に、単純な型を返す必要があるが「long double」などのプロセッサレジスタに収まらない場合は、接尾辞「fpret」を持つ関数が使用されます。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject @end @implementation TestClass + (long double)someMethod { return 0.0; } @end int main(int argc, const char * argv[]) { // objc_msgSend_fpret() [TestClass someMethod]; return 0; }
      
      







もちろん、Objective-Cのメッセージメカニズムについてはいつでも詳細を伝えることができますが、今すぐ終了したいと思います。 したがって、質問がある場合は Objective-C Runtimeのソースコードで質問に対する回答をいつでも見つけることができます。 それでは先に進みましょう。



取得した知識を適用して結論を​​導きます





私たちが時間を無駄にしているのか、それとも本当にビジネスをしているのかというトピックについて、長い間話すことができます。 ただし、たとえば、現在、Objective-Cでリモートプロシージャコールを実装することは、非常に簡単なタスクです。 少なくとも、あなたは同意しなければなりません、あなたはすぐにこれを実装する方法を想像しました:



  1. ネットワーク経由でメソッド名を取得する
  2. セレクターを取る
  3. 目的の機能を呼び出す
  4. 利益!!!!




ただし、私たちのビジネスはさらに理解し、不必要な質問をしないことです。ただし、1つだけを除きます。実行時にクラスにメソッドを直接追加することは可能ですか もちろんできます!



Objective-Cでアプリケーションを開発した経験のある人は、カテゴリなどのメカニズムをすぐに思い出しました。 カテゴリを使用すると、ソースファイルがなくてもクラスの機能を拡張できます。 たとえば、この方法では、NSObjectクラスに新しいメソッドを追加できます。



 #import <Foundation/Foundation.h> @interface NSObject (APObjectMapping) + (NSMutableDictionary *)objectMapping; - (instancetype)initWithDictionary:(NSDictionary *)dictionary; - (NSDictionary *)mapToDictionary; @end
      
      







明らかに、実行時に、NSObjectクラスのディスパッチテーブルに新しいメソッドが追加されるだけです。 これは、NSObjectから継承された他のすべてのクラスも、追加したメソッドを受け取ることを意味します。 美しさなど!



C言語のみを使用して、カテゴリを使用せずに同じことを実行してみましょうこれを行うには、 class_addMethod関数を使用します。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> void appleSecret(id self, SEL _cmd) { NSLog(@"Tim Cook is so gay..."); } int main(int argc, const char * argv[]) { class_addMethod([NSObject class], @selector(appleSecret), (IMP)appleSecret, "v@:"); NSObject * myObj = [[NSObject alloc] init]; [myObj performSelector:@selector(appleSecret)]; return 0; }
      
      







class_addMethod()関数の最初の3つのパラメーターを使用すると、すべてが明確になりますが、4番目は関数の引数の指定です。 この場合、「v」は戻り値の型がvoid、「@」が型「object」の関数の最初のパラメーター、「:」が型「selector」の関数の2番目のパラメーターであることを意味します。 たとえば、関数がint型の別のパラメーターを受け取った場合、その仕様は「v @:i」のようになります。



まとめると





メッセージメカニズムは、Objective-C言語の中心であり、Java、C#、Pythonなどで慣れ親しんでいるすべての魅力を最も一般的なCレベルの言語で提供するのはこのメカニズムです。 このメカニズムに対処した後、カテゴリなどのObjective-Cの特定の機能がどのように機能するかを理解し始めました。 また、必要に応じて、 プロトコルの配置方法を理解できます



これで、一時的にメッセージエンジンを終了し、このシリーズの他の基本的なメカニズムを整理することをお勧めします。これについては、このシリーズの今後の記事で説明します。



All Articles