Objective-Cのメタクラス

この記事は、 Objective-Cのメタクラスとは何ですか?



翻訳は著作権です。 マイナーな追加は主にAppleのドキュメントに関するもので、尊敬されている読者の便宜のためにのみ追加されます。 記事の翻訳をコピーする場合、元の翻訳へのリンクが必要です。 共同作業を尊重しましょう。





小さな紹介


このようなObjective-C言語の概念をメタクラスとして考えてみましょう。

Objective-Cを学ぶことにした初心者のように、私は1つの詳細に興味を持ちました。

クラスが同時にオブジェクトであり、クラスメソッドを呼び出すメッセージを返すことができる場合(戻り値の前に「+」があります)、そのようなオブジェクトクラスは何ですか?



まず、クラスメソッドがインスタンスメソッドとどのように異なるかを覚えておいてください。



@interface MyClass : NSObject + (void)aClassMethod; //  - (void)anInstanceMethod; //  @end ................................................. [MyClass aClassMethod]; //   + (void)aClassMethod; MyClass *object = [[MyClass alloc] init]; //   MyClass [object anInstanceMethod]; //   - (void)anInstanceMethod;
      
      







ご覧のとおり、クラスメソッドを呼び出すために、クラスの個別のインスタンスを作成する必要はありません。 メッセージを送信するときは、クラス名を指定するだけです。



実行時に手動でクラスを作成する


したがって、Objective-Cの各クラスには、独自の対応するメタクラスがあります。 実行時にメタクラスを見てみましょう。

実行時に手動でクラスを作成します。

次のコードは、NSErrorのRuntimeErrorSubclassサブクラスをサブクラス化し、それにレポートメソッドを追加します



 Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0); class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:"); objc_registerClassPair(newClass);
      
      







class_addMethod関数を使用して追加するレポートメソッドはReportFunction関数を使用します( impは、新しいメソッドを実装する関数へのポインターです。関数は、少なくとも2つのパラメーターselfおよび_cmdを受け入れる必要があります)。



 void ReportFunction(id self, SEL _cmd) { NSLog(@"This object is %p.", self); NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]); Class currentClass = [self class]; for (int i = 1; i < 5; i++) { NSLog(@"Following the isa pointer %d times gives %p", i, currentClass); currentClass = object_getClass(currentClass); } NSLog(@"NSObject's class is %p", [NSObject class]); NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class])); }
      
      







外見的には、すべてがとてもシンプルに見えます。 わずか3ステップで、クラスを作成しました。

1. objc_allocateClassPairを使用してクラスを作成します

2. class_addMethodを使用してメソッドを追加しました(必要に応じて、 class_addIvarを使用して変数を追加できます)

3. objc_registerClassPairを使用してクラスを登録します



同時に、「クラスペア」とは何かという疑問が生じます。 もちろん、Appleのドキュメントとobjc_allocateClassPair関数の説明を見ると、クラスだけでなく不思議なメタクラスも作成されていることがわかります。 ここにいくつかのクラスがあります。

ただし、メタクラスの概念を理解するには、いくつかの基本を理解する必要があります。



データ構造をオブジェクトと見なすことができる場合


各オブジェクトにはクラスがあります。 これはオブジェクト指向プログラミングの基本概念ですが、Objective-Cではデータ構造の重要な部分でもあります。 クラスへのポインターを持つデータ構造は、オブジェクトと見なすことができます。



Objective-Cでは、オブジェクトクラスはisaポインターを使用して定義されます。



一般に、Objective-Cの基本的なオブジェクト定義は次のようになります。



 typedef struct objc_object { Class isa; } *id;
      
      







つまり、クラスへのポインターで始まるデータ構造は、オブジェクトと見なすことができます。



Objective-Cのオブジェクトの最も重要な機能は、オブジェクトにメッセージを送信する機能です。



 [@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
      
      







これは、Objective-Cオブジェクト(上記のNSCFStringなど )にメッセージを送信すると、ランタイムがisaクラスへのポインターをたどり、オブジェクトのクラス(この場合はNSCFStringクラスへ)につながるためです。 クラスには、このクラスのすべてのオブジェクトに適用できるメソッドのリストと、継承されたメソッドを表示するスーパークラス(またはスーパークラス)へのポインターが含まれています。 ランタイムは、クラスおよびスーパークラスメソッドのリストを調べて、メッセージセレクターに一致するメソッドを見つけます(上記の場合、 writeToFile:atomically:encoding:error on NSString )。 次に、ランタイムはこのメソッドの関数( IMP )を呼び出します。



重要な点は、クラスがオブジェクトに送信できるメッセージを決定することです。



メタクラスとは何ですか?


既にご存じのとおり、Objective-Cのクラスもオブジェクトです。 これは、クラスにメッセージを送信できることを意味します。



 NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
      
      







この場合、 defaultStringEncodingメッセージはNSStringクラスに送信されます。



これは、Objective-Cのすべてのクラスもオブジェクトであるため機能します。 これは、クラスの構造もクラスisaへのポインターで始まるため、その構造は前述のオブジェクトの構造に似ていることを意味し、構造の次のフィールドはスーパークラス(または基本クラスの場合はnil)へのポインターでなければなりません。



クラスの構造はランタイム環境のバージョンによって異なる場合がありますが、それらはすべてisaクラスへのポインターとスーパークラスへのポインターで始まります。



 typedef struct objc_class *Class; struct objc_class { Class isa; Class super_class; /*      ... */ };
      
      







ただし、Classメソッドを呼び出すには、Class isaへのポインターがClass構​​造体を指している必要があり、このClass構​​造体には、Classが呼び出すことができるメソッドのリストが含まれている必要があります。



これにより、メタクラスの定義につながります。メタクラスはClassオブジェクトのクラスです。



簡単に言えば:

-クラスのインスタンスにメッセージ送信すると、メッセージはクラスオブジェクトのメソッドリストに表示されます。

-メッセージをClassに(または、現在わかっているようにClassオブジェクトに )送信すると、そのようなメッセージはmetaclassメソッドのリストに表示されます



クラスメソッドを格納するため、メタクラスが必要です。 各クラスには潜在的に一意のクラスメソッドのリストがあるため、各クラスには一意のメタクラスが必要です。



メタクラスのクラスは何ですか?


前述のクラスと同様に、メタクラスもオブジェクトです。 つまり、メタクラスメソッドを呼び出すこともできます。 一般的に、これはメタクラスにもクラスが必要であることを意味します。



すべてのメタクラスは、ベースメタクラス(クラス階層の最上位クラスのメタクラス)をクラスとして使用します。 これは、NSObject(クラスの大部分)から継承するすべてのクラスについて、メタクラスはクラスとしてメタクラスNSObjectを持っていることを意味します。



クラスのベースメタクラスをクラスとして使用するすべてのメタクラスについては、ベースメタクラスはすべて独自のクラスになります(それらのisaポインターはそれらを直接指す)。 これは、NSObjectメタクラスのisaポインターが自分自身を指していることを意味します。



クラスとメタクラスの階層。


super_classポインターを使用するスーパークラスポインターを持つクラスのように、メタクラスには、クラスのスーパークラスメタクラスへの独自のsuper_classポインターがあります



基本メトクラスのスーパークラスへのポインターは、それ自体を指します。



この階層の結果、階層内のすべてのインスタンス、クラス、およびメタクラスが基本クラス階層から継承されます。



NSObject階層内のすべてのインスタンス、クラス、およびメタクラスの場合、これはNSObjectインスタンスのすべてのメソッドもそれらに適用できることを意味します。



これらはすべてテストとしてはかなり良いように見えますが、グレッグパーカーはインスタンス、クラス、メタクラスの階層を示す優れた図を提供しました。



画像



実験的確認。


上記を確認するために、最初に書いたReportFunction関数の出力を見てみましょう。

私たちの目標は、 ISAのサインに従い、発見されるものを書き留めることです。



ReportFunctionを使用するには、動的に作成されたクラスをインスタンス化し、 レポートメソッドを呼び出す必要があります



 id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil]; [instanceOfNewClass performSelector:@selector(report)]; [instanceOfNewClass release];
      
      







reportメソッドの宣言がないため、 performSelector関数を使用して呼び出します。そのため、コンパイラーは警告を出しません。



次に、 ReportFunctionはisaポインターを渡し、どのオブジェクトがクラスとして使用されているか、どのオブジェクトがメタクラスとメタクラスクラスとして使用されているかを示します。



オブジェクトクラスの取得: isaポインターはクラスの保護されたメンバーであるため、 ReportFunction関数はobject_getClass関数を使用してisaクラスへのポインターを走査します(チェーン内の他のオブジェクトのisaポインターに直接アクセスすることはできません)。

また、ReportFunctionは、 クラスオブジェクトのクラスメソッドの呼び出しを使用しません。 クラスメソッドの呼び出しはメタクラスを返さず、代わりにクラスを再度返すためです(たとえば、[NSStringクラス]は、メタクラスの代わりにNSStringクラスを再度返します)。

object_getClassは、引数として渡されたオブジェクトをインスタンスとするクラスのオブジェクトを返します。



 Class object_getClass(id object)
      
      







プログラムの結論:

 This object is 0x10010c810. Class is RuntimeErrorSubclass, and super is NSError. Following the isa pointer 1 times gives 0x10010c600 Following the isa pointer 2 times gives 0x10010c630 Following the isa pointer 3 times gives 0x7fff71038480 Following the isa pointer 4 times gives 0x7fff71038480 NSObject's class is 0x7fff710384a8 NSObject's meta class is 0x7fff71038480
      
      







受信したアドレスを見る:



オブジェクト:0x10010c810。

クラス:0x10010c600。

メタクラス:0x10010c630。

メタクラスクラス(メタクラスNSObject)0x7fff71038480。

NSObjectメタクラスクラスは、NSObjectメタクラスそのものです( isaは自身を指します)。



アドレス値は、前述のように、メタクラスNSObjectからのメタクラスの階層を示すことを除いて、実際には重要ではありません。



結論


メタクラスは、Classオブジェクトのクラスです。 各クラスには固有のメタクラスがあります(各クラスには独自のメソッドのリストがあるため)。



メタクラスは常に、Classオブジェクトに、ベースクラスのすべての必要な変数とメソッド、およびクラスのすべてのメソッドがあることを保証します。 NSObjectを継承するクラスの場合、これは、NSObjectクラスのすべてのインスタンスメソッドとNSObjectのプロトコルメソッドが、派生クラスとメタクラスのすべてのオブジェクトに対して定義されることを意味します。



すべてのメタクラスは、NSObjectクラスのベースメタクラスをクラスとして使用します。



All Articles