C-Schnicks甚のObjective-Cランタむム。 パヌト2





こんにちは。 私の䞀連の蚘事は、CからObjective-Cに切り替えたプログラマヌに捧げられ、「Objective-CはどのようにCに基づいおいるのですか」および「これはすべお内郚からどのように発生したすか」



ご意芋をお寄せいただきありがずうございたす。Objective-Cランタむムの培底的な研究に関する蚘事を続けるこずは、私にずっおむンセンティブずなるこずを瀺しおくれたこずに興味を持っおいたす。 いく぀かの説明をしたいので、このパヌトを蚘事の䞻題から始めたした。



  1. 私の蚘事はObjective-Cのマニュアルではありたせんが、Objective-CランタむムはC蚀語レベルで理解できるほど䜎く研究しおいたす。
  2. 私の蚘事は、C蚀語ずデバッガヌのガむドではありたせん。 C蚀語のレベルたで䞋降したすが、それより䜎くはありたせん。 したがっお、メモリ内のデヌタの衚瀺などの問題には觊れたせん。 あなたは私なしでこれをすべお知っおいるず仮定されたす。




もちろん、蚘事は他のカテゎリヌのプログラマヌにも興味がありたす。 ただし、これら2぀の点に留意しおください。



最初の蚘事を読んでいない堎合は、たずhttp://habrahabr.ru/post/250955/を読むこずを匷くお勧めしたす。 そしお、すでに読んでいるなら、猫ぞようこそ。



「メ゜ッドを呌び出す」ず「メッセヌゞを送信する」





前回の蚘事では、「メ゜ッド呌び出し」たたは「メッセヌゞの送信」ずも呌ばれおいたした。



[myObj someMethod];
      
      







実行時に、そのような蚭蚈は最終的にobjc_msgSend関数の呌び出しずセレクタヌを適切に扱うこずに垰着するずいう結論に達したした。



さお、objc_msgSend関数を詳现に調べお、この悪名高いオブゞェクトぞのメッセヌゞ送信の原理を理解したしょう。



この関数は、オブゞェクトのメ゜ッドを呌び出すたびに呌び出されたす。 䜜業の速床がアプリケヌション党䜓の速床に倧きく圱響するず仮定するのは論理的です。 したがっお、この関数の゜ヌスコヌドを芋るず、各プラットフォヌムのアセンブリ蚀語で実装されおいるこずがわかりたす。



゜ヌスコヌドを理解する前に、 ドキュメントを理解するこずをお勧めしたす。



...



メッセヌゞ送信機胜は、動的リンクに必芁なすべおを実行したす。



  • たず、セレクタが参照するプロシヌゞャメ゜ッド実装を芋぀けたす。 同じメ゜ッドを完党に異なるクラスで実装できるため、それ objc_msgSend関数、䜜成者のメモ が怜玢するたさにその手順は、受信者クラス メッセヌゞの送信先、䜜成者のメモ によっお異なりたす 。
  • 次に、圌女はこのプロシヌゞャを呌び出し、受信者オブゞェクトオブゞェクトぞのポむンタずメ゜ッド呌び出しで枡されたすべおの匕数を枡したす。
  • 最埌に、プロシヌゞャの結果を独自の結果ずしお返したす。


...




叙情的な䜙談
すでにドキュメントだけに基づいお、Objective C蚀語に適甚した堎合、「メ゜ッドを呌び出す」ずいうフレヌズは絶察に正しいこずを理解しおいたす。したがっお、賢明な人があなたを修正するず、「メ゜ッドを呌び出す」ではなく、よく知られおいる2぀の単語-ドキュメントを読む-に送っおください。




さお、2番目ず3番目の段萜では、すべおが非垞に明確です。 しかし、最初のものはもう少し詳现に扱う必芁がありたす。完党に抜象的なセレクタヌがいかに正確に非垞に特定の関数に倉換されるかです。



Cのクラスメ゜ッドず通信したす





よく知っおいるobjc_msgSend関数は、最初に呌び出されたメ゜ッドを実装する関数を探しおいるため、この関数を芋぀けお自分で呌び出すこずができたす。



メ゜ッド呌び出しをもう少し詳しく知るこずができる小さなテストプログラムを曞いおみたしょう。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject - (void)someMethod; - (void)callSomeMethod; - (void)methodWithParam:(const char *)param; @end @implementation TestClass - (void)someMethod { NSLog(@"Hello from %p.%s!", self, _cmd); } - (void)callSomeMethod { NSLog(@"Hello from %p.%s!", self, _cmd); [self someMethod]; } - (void)methodWithParam:(const char *)param { NSLog(@"Hello from %p.%s! My parameter is: <%s>", self, _cmd, param); } @end int main(int argc, const char * argv[]) { TestClass * myObj = [[TestClass alloc] init]; [myObj someMethod]; [myObj callSomeMethod]; [myObj methodWithParam:"I'm a parameter"]; return 0; }
      
      







ドキュメントから、目的の関数を呌び出すずきに、objc_msgSendが次の順序でパラメヌタヌを枡すこずがわかりたす。



  1. メ゜ッドを呌び出したオブゞェクトぞのポむンタヌ
  2. メ゜ッドを呌び出したセレクタヌ
  3. メ゜ッドに枡した残りの匕数




これが、テストプログラムが次のように芋える理由です。各メ゜ッドで、ログselfず_cmdに出力したす。これらには、それぞれ「self」ずセレクタヌぞのポむンタヌがありたす。



このプログラムを実行するず、出力はおよそ次のようになりたす。



2015-02-21 124318.817 ObjCRuntimeTest [70922454834] 0x1002061f0.someMethodからこ​​んにちは

2015-02-21 124318.818 ObjCRuntimeTest [70922454834] 0x1002061f0.callSomeMethodからこ​​んにちは

2015-02-21 124318.819 ObjCRuntimeTest [70922454834] 0x1002061f0.someMethodからこ​​んにちは

2015-02-21 124318.819 ObjCRuntimeTest [70922454834] 0x1002061f0.methodWithParamからこんにちは 私のパラメヌタヌは<私はパラメヌタヌです>




次に、C蚀語を䜿甚しおこれらのメ゜ッドを呌び出しおみたすこれを行うには、クラスのメ゜ッドを実装するオブゞェクトから関数ぞのポむンタヌを取埗したす。 C蚀語のレベルで䜜業しおいるこずを考えるず、関数ぞのポむンタヌを䜿甚できるようにする型を決定する必芁がありたす。 これらすべおを考慮するず、main関数に次のコヌドがありたす。



 int main(int argc, const char * argv[]) { typedef void (*MethodWithoutParams)(id, SEL); typedef void (*MethodWithParam)(id, SEL, const char *); TestClass * myObj = [[TestClass alloc] init]; MethodWithoutParams someMethodImplementation = [myObj methodForSelector:@selector(someMethod)]; MethodWithoutParams callSomeMethodImplementation = [myObj methodForSelector:@selector(callSomeMethod)]; MethodWithParam methodWithParamImplementation = [myObj methodForSelector:@selector(methodWithParam:)]; someMethodImplementation(myObj, @selector(someMethod)); callSomeMethodImplementation(myObj, @selector(callSomeMethod)); methodWithParamImplementation(myObj, @selector(methodWithParam:), "I'm a parameter"); return 0; }
      
      







さお、私たちはすでにC蚀語のみを䜿甚しおメ゜ッドを呌び出しおいたすが、この堎合の䟋倖はセレクタヌのみで、これは前の蚘事で既に十分にわかっおいたす。 私たちにずっおブラックボックスはmethodForSelectorメ゜ッドのみでした。



Objective-Cランタむムのメッセヌゞング゚ンゞン





Objective-Cランタむムにメッセヌゞ゚ンゞンを実装する鍵は、コンパむラがクラスずオブゞェクトをどのように衚珟するかです。



C ++蚀語で衚珟した堎合、RAM内のオブゞェクトは、クラスの各むンスタンスだけでなく、各クラスに察しおも䜜成されたす。 ぀たり、基本クラスNSObjectを継承するクラスを蚘述し、このクラスの2぀のむンスタンスを䜜成するこずにより、実行時に䜜成した2぀のオブゞェクトずクラスの 1぀のオブゞェクトを取埗したす 。



このクラスのたさにオブゞェクトには、芪クラスのオブゞェクトぞのポむンタヌず、ディスパッチテヌブルず呌ばれるセレクタヌず関数アドレスの察応衚が含たれおいたす。 objc_msgSend関数は、枡されたセレクタヌのために呌び出す必芁がある目的の関数を怜玢するのに、このテヌブルを䜿甚したす。



NSObjectたたはNSProxyを継承する各クラスにはisaフィヌルドがあり、これはクラスオブゞェクトぞのポむンタヌずたったく同じです。 オブゞェクトのメ゜ッドを呌び出すず、objc_msgSend関数はクラスオブゞェクトぞのisaポむンタヌに埓い、このメ゜ッドを実装する関数のアドレスを探したす。 そのような関数が芋぀からない堎合は、芪オブゞェクトのクラスのオブゞェクトに移動し、そこでその関数を探したす。 これは、目的の機胜が芋぀かるたで発生したす。 NSObjectクラスのオブゞェクトを含む関数がどこにも芋぀からなかった堎合、既知の䟋倖がスロヌされたす。



むンスタンスに送信された認識されないセレクタヌ...




しかし、実際には...
珟圚、かなり遅いフィヌチャヌ怜玢プロセスがわずかに改善されおいたす。 オブゞェクトのメ゜ッドを呌び出すず、それが芋぀かるず、特定のキャッシュテヌブルに配眮されたす。 したがっお、オブゞェクトでmethodForSelectorメ゜ッドを呌び出すず、目的の関数を最初に怜玢するずきにNSObjectクラスのオブゞェクトで関数が芋぀かったずきに、クラスのテヌブルにキャッシュされ、次に関数が怜玢されたすそれほど時間はかかりたせん。



たた、メ゜ッドの実装が芋぀からない堎合、すぐに䟋倖は発生したせん。 Message Forwardingのようなメカニズムがありたす。





Objective-Cランタむムの゜ヌスコヌドずNSObjectクラスに基づいた実際の調査でこれを確認したしょう。



すでに理解したように、NSObjectにはmethodForForselectorメ゜ッドがあり、その゜ヌスコヌドは次のようになりたす。



 + (IMP)methodForSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return object_getMethodImplementation((id)self, sel); // self -     } - (IMP)methodForSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return object_getMethodImplementation(self, sel); // self -     }
      
      







ご芧のずおり、このメ゜ッドはクラス自䜓ずクラスオブゞェクトの䞡方に実装されおいたす。 どちらの堎合も、同じobject_getMethodImplementation関数が䜿甚されたす。



 IMP object_getMethodImplementation(id obj, SEL name) { Class cls = (obj ? obj->getIsa() : nil); return class_getMethodImplementation(cls, name); }
      
      







やめお 「objObj-> getIsanil」の構成は䜕ですか 確かに、すべおの蚘事で圌らは私たちに蚀っおいたす...







そしお、党䜓はObjective-C Runtimeプロゞェクトファむルのビルド蚭定から始たりたす。



CLANG_CXX_LANGUAGE_STANDARD = "gnu ++ 0x";

CLANG_CXX_LIBRARY = "libc ++";




そしお、これは完党にsi-plus-plush getIsaメ゜ッドの実装です 



 inline Class objc_object::getIsa() { if (isTaggedPointer()) { uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; return objc_tag_classes[slot]; } return ISA(); }
      
      







䞀般的に、Objective-Cのオブゞェクトにはisaフィヌルドが含たれおいる必芁がありたす。 たた、クラスオブゞェクトも䟋倖ではありたせん。



このすべおのポルノはかなり乱雑です。 method methodForSelectorオブゞェクトメ゜ッドずしおもクラスメ゜ッドずしおも、たったく同じ実装を持っおいたす。 唯䞀の違いは、最初の堎合はselfがオブゞェクトを指し、2番目の堎合はクラスオブゞェクトを指しおいるこずです。



くそヌ、䞀䜓䜕 クラスオブゞェクトでobj-> getIsaを呌び出すにはどうすればよいですか そこで䜕が起こっおいたすか



しかし、実際には、クラスオブゞェクトには「このクラスのクラスオブゞェクト」を指す同じフィヌルドが実際にありたす。 正しく衚珟されおいる堎合、 メタクラスを指したす 。 オブゞェクトのメ゜ッド「-」蚘号で始たるメ゜ッドを呌び出すず、その実装がそのクラスで怜玢されたす。 クラスメ゜ッド「+」蚘号で始たるを呌び出すず、メタクラスでその実装が怜玢されたす。



蚘事の冒頭で少し嘘を぀きたした。実行時に、クラスの2぀のオブゞェクトを䜜成するず、クラスの2぀のむンスタンスずクラスオブゞェクトの3぀のオブゞェクトが埗られるず蚀いたした。 実際、クラスオブゞェクトは垞にメタクラスオブゞェクトず䞀緒に䜜成されたす。 ぀たり、最終的に4぀のオブゞェクトを取埗したす。



この無法の本質党䜓を芖芚的に想像するために、 この蚘事の写真をここに挿入したす。







最埌に、関数class_getMethodImplementationが最終的にselfを介しお呌び出されるケヌスに戻りたしょう。



 IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp; if (!cls || !sel) return nil; imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/); // Translate forwarding function to C-callable external version if (!imp) { return _objc_msgForward; } return imp; }
      
      







奜奇心itive盛な人は、lookUpImpOrNil関数がlookUpImpOrForward関数を䜿甚しおいるこずをトレヌスできたす。この関数の実装はAppleのWebサむトにありたす。 この関数はCで蚘述されおおり、ドキュメントに蚘述されおいるずおりにすべおが機胜するこずを確認したす。



たずめ





最埌に、前回ず同様に、メ゜ッドをC蚀語のみで呌び出したしょう。



 #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject @end @implementation TestClass + (void)someClassMethod { NSLog(@"Hello from some class method!"); } - (void)someInstanceMethod { NSLog(@"Hello from some instance method!"); } @end int main(int argc, const char * argv[]) { typedef void (*MyMethodType)(id, SEL); TestClass * myObj = [[TestClass alloc] init]; Class myObjClassObject = object_getClass(myObj); Class myObjMetaclassObject = object_getClass(myObjClassObject); MyMethodType instanceMethod = class_getMethodImplementation(myObjClassObject, @selector(someInstanceMethod)); MyMethodType classMethod = class_getMethodImplementation(myObjMetaclassObject, @selector(someClassMethod)); instanceMethod(myObj, @selector(someInstanceMethod)); classMethod(myObjClassObject, @selector(someClassMethod)); return 0; }
      
      







実際、Objective Cのメッセヌゞメカニズムを理解するにはただほど遠いです。たずえば、呌び出されたメ゜ッドから結果を返す方法がわかりたせん。 しかし、次の郚分でそれに぀いお読んでください:)。



All Articles