Objective-C Runtime(転載)

7063 ワード

これは比較的一般的なiOSの実行時に紹介された文章で、転載先は以下の通りです:Objective-C Runtime
Objective-C
Objective-CはC言語を拡張し,オブジェクト向け特性とSmalltalk式のメッセージングメカニズムを加えた.この拡張の核心はCとコンパイル言語で書かれたRuntimeライブラリです.これはObjective-Cのオブジェクト向けと動的機構の礎である.Objective-Cは動的言語であり、コンパイラだけでなく、クラスとオブジェクトを動的に作成し、メッセージングと転送を行うランタイムシステムも必要であることを意味します.Objective-CのRuntimeメカニズムを理解することで、この言語をよりよく理解することができ、適切な時に言語を拡張し、システムレベルからプロジェクトの設計や技術問題を解決することができます.Runtimeを理解するには、まずそのコアであるメッセージング(Messaging)を理解します.
メッセージング
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” – that is what the kernal[sic] of Smalltalk is all about... The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
Alan KayはSmalltalkのコアがオブジェクト向けではなく、オブジェクト向けがthe lesser ideasであり、メッセージングこそthe big ideaであることを何度も強調したことがある.多くの言語、例えばCでは、メモリのある点にジャンプしてコードを実行する方法を呼び出します.コンパイル時に決定されるので、ダイナミックな特性はありません.一方、Objective-Cでは、[object foo]構文はfooという方法のコードをすぐに実行しません.実行時にobjectにfooというメッセージを送信します.このメッセージは、objectによって処理されるか、別のオブジェクトに転送されるか、受信していないふりをして相手にしないかもしれません.複数の異なるメッセージは、同じ方法に対応して実装されてもよい.これらはすべてプログラムの実行時に決定されます.実際、コンパイル時に書かれたObjective-C関数呼び出しの構文は、Cの関数呼び出し- objc_msgSend()に翻訳されます.たとえば、次の2行のコードは等価です.
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

メッセージングの鍵は、objc_objectのisaポインタとobjc_classのclass dispatch tableに隠されている.objc_objectobjc_classおよびOjbc_methodObjective-Cでは、クラス、オブジェクト、およびメソッドは、objc/objc.hヘッダファイルでは、定義を見つけることができます.
struct objc_object { 
    Class isa OBJC_ISA_AVAILABILITY;
};

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists 
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
#endif
};

struct objc_method_list {
   struct objc_method_list *obsolete;
   int method_count;
 #ifdef __LP64__ 
    int space;
#endif 
/* variable length structure */ 
    struct objc_method method_list[1];
};

struct objc_method { 
  SEL method_name; 
  char *method_types; /* a string representing argument/return types */
  IMP method_imp;
};
objc_method_listは本質的にobjc_method要素を有する可変長の配列である.1つのobjc_method構造体には、関数名、すなわちSELがあり、関数タイプを表す文字列(Type Encodingを参照)、および関数の実装IMPがある.これらの定義から、メッセージを送信してもobjc_msgSendが何をしたのかがわかります.objc_msgSend(obj, foo)という例を挙げると、
  • まずobjのisaポインタによってそのclassを見つける.
  • classのmethod listでfooを探します;
  • classにfooがない場合は、そのsuperclassに探し続けます.
  • fooという関数が見つかれば、その実装IMPを実行する.

  • しかし、このような実現には問題があり、効率が低い.しかし、1つのclassは往々にして20%の関数だけが頻繁に呼び出され、総呼び出し回数の80%を占める可能性があります.各メッセージは、objc_method_listを1回巡回する必要があるのは合理的ではありません.頻繁に呼び出される関数をキャッシュすると、関数クエリーの効率が大幅に向上します.これはobjc_classのもう一人の重要なメンバーobjc_cacheがしたことです.fooを探した後、fooのmethod_nameをkeyとし、method_impをvalueとして保存します.再びfooメッセージを受信すると、objc_method_listを遍歴することを避けるためにcacheで直接見つけることができる.
    動的メソッドの解析と転送
    上の例ではfooが見つからなかったら何が起こるのでしょうか.通常、プログラムは実行時にunrecognized selector sent to...の異常を削除して放出します.しかし、例外が投げ出される前に、Objective-Cの実行時に3回の救済プログラムの機会が与えられます.
  • Method resolution
  • Fast forwarding
  • Normal forwarding

  • Method Resolution
    まず、Objective-Cの実行時に+resolveInstanceMethod:または+resolveClassMethod:が呼び出され、関数実装を提供する機会があります.関数を追加してYESに戻ると、実行時にメッセージ送信のプロセスが再起動されます.fooを例にとると、次のように実現できます.
    void fooMethod(id obj, SEL _cmd) {
       NSLog(@"Doing foo");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)aSEL{ 
        if(aSEL == @selector(foo:)){
           class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:"); return YES; 
        }
       return [super resolveInstanceMethod];
    }
    

    Core Dataはこの方法に役立ちます.NSManagedObjectsのpropertiesのgetterとsetterは、実行時に動的に追加されます.resolveメソッドがNOを返すと、実行時は次のステップに移ります.メッセージ転送(Message Forwarding).PS:iOS 4.3には、imp_implementationWithBlock()のようにimpを接頭辞とするメソッドが多く追加されています.上記の例は、次のように書き換えることができます.
    IMP fooIMP = imp_implementationWithBlock(^(id _self) {
         NSLog(@"Doing foo");
    });
    
    class_addMethod([self class], aSEL, fooIMP, "v@:");
    

    Fast forwarding
    ターゲットオブジェクトが-forwardingTargetForSelector:を実現すると、Runtimeはこのメソッドを呼び出し、このメッセージを他のオブジェクトに転送する機会を与えます.
    - (id)forwardingTargetForSelector:(SEL)aSelector{ 
        if(aSelector == @selector(foo:)){
             return alternateObject; 
          } 
        return [super forwardingTargetForSelector:aSelector];
    }
    

    この方法がnilとselfでない限り、メッセージ送信のプロセス全体が再起動され、もちろん送信されたオブジェクトはあなたが返したオブジェクトになります.そうでない場合、Normal Fowardingが続行されます.
    ここではFastと言いますが、次の転送メカニズムを区別するためです.このステップでは新しいオブジェクトは作成されませんが、次の転送ではNSInvocationオブジェクトが作成されるので、比較的高速です.
    Normal forwarding
    このステップはRuntimeが最後にあなたを救うチャンスです.まず、-methodSignatureForSelector:メッセージを送信して、関数のパラメータと戻り値のタイプを取得します.-methodSignatureForSelector:がnilに戻ると、Runtimeは-doesNotRecognizeSelector:メッセージを送信し、プログラムはこの時点で停止します.関数署名が返されると、RuntimeはNSInvocationオブジェクトを作成し、ターゲットオブジェクトに-forwardInvocation:メッセージを送信します.NSInvocationは、selectorやパラメータなどの情報を含むメッセージの記述である.-forwardInvocation:で送信されたNSInvocationオブジェクトを変更し、-invokeWithTarget:メッセージを送信して新しいターゲットを送信することができます.
    - (void)forwardInvocation:(NSInvocation *)invocation{
           SEL sel = invocation.selector;
           if([alternateObject respondsToSelector:sel]) {
                 [invocation invokeWithTarget:alternateObject]; 
            } else {
                 [self doesNotRecognizeSelector:sel]; 
          }
    }
    

    Cocoaでは、Proxies、NSUndoManager、Responder Chainなどの言語を拡張するために、メッセージングメカニズムが利用されています.NSProxyは、エージェントとしてメッセージを転送するために使用されます.NSUndoManagerはメッセージを切り取ってから送信します.一方、Responder Chainは、メッセージが適切な応答者に転送されることを保証する.
    まとめ
    Objective-Cでオブジェクトにメッセージを送信するには、次の手順に従います.
  • オブジェクトクラスのdispatch tableでメッセージを見つけようとします.見つかったら、対応する関数IMPにジャンプして実装コードを実行します.
  • が見つからない場合、Runtimeは+resolveInstanceMethod:または+resolveClassMethod:にresolveに行こうとするメッセージを送信します.
  • resolveメソッドがNOを返すと、Runtimeは-forwardingTargetForSelector:を送信し、このメッセージを別のオブジェクトに転送することを許可します.
  • 新しいターゲットオブジェクトが戻ってこない場合、Runtimeは-methodSignatureForSelector:および-forwardInvocation:メッセージを送信します.-invokeWithTarget:メッセージを送信して、手動でメッセージを転送したり、-doesNotRecognizeSelector:を送信して異常を投げ出したりすることができます.

  • Objective-Cのruntime特性を利用して、私たちは自分で言語を拡張して、プロジェクト開発の中のいくつかの設計と技術の問題を解決することができます.次の記事では、Method Swizzzlingテクノロジーと、Method SwizzlingをLoggingする方法について説明します.
    Reference
    Message forwarding Objective-c-messaging The faster objc_msgSend Understanding objective-c runtime