Objective-C Runtimeの4:Method Swizzling

4577 ワード

南峰子先生のブログを読み返し、先生のRuntimeの説明には100%心を通わせるとは言えませんが、上手に身につけることができて、書き直して、後の自分に残して調べます.

記録は6つの部分に分かれています。

  • Objective-C Runtimeの1つ:クラスとオブジェクト.クリックすると
  • Objective-C Runtimeの2:メンバー変数と属性.クリックすると
  • Objective-C Runtimeの3:メソッドとメッセージ.クリックすると
  • Objective-C Runtimeの4:Method Swizzling.クリックすると
  • Objective-C Runtimeの5:プロトコルと分類.クリックすると
  • Objective-C Runtimeの6:拾遺.クリックすると
  • この記事には、第4部が記載されています.
    理解Method Swizzlingruntimeメカニズムを学ぶ良い機会である.ここではあまり整理しないで、Matttt Thompsonがnshipsterに発表したMethod Swizzlingの一文だけを翻訳します.Method Swizzlingは、selectorの実際の実装を変更する技術である.この技術により,実行時にクラスの分割発表におけるselectorに対応する関数を修正することによって,方法の実装を修正することができる.
    たとえば、プログラム内の各view controllerがユーザーに表示された回数を追跡したいです.もちろん、各view controllerのviewDidAppearに追跡コードを追加することができます.しかし、これは面倒で、view controllerごとに重複するコードを書く必要があります.サブクラスを作成することは実装方法かもしれませんが、UIdiewController、UItableView Controller、UInavigationController、その他のUIKETのview controllerのサブクラスを同時に作成する必要があります.これは、同じように多くの重複コードを生成します.
    この場合、コードに示すように、Method Swizzlingを使用できます.
    #import 
    @implementation UIViewController (Tracking)
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];         
            // When swizzling a class method, use the following:
            // Class class = object_getClass((id)self);
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(xxx_viewWillAppear:);
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            BOOL didAddMethod = class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
            if (didAddMethod) {
                class_replaceMethod(class,
                    swizzledSelector,
                    method_getImplementation(originalMethod),
                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    #pragma mark - Method Swizzling
    - (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"viewWillAppear: %@", self);
    }
    @end
    

    ここでは、method swizzlingにより、UIViewControllerの@selector(viewWillAppear:)に対応する関数ポインタを修正し、カスタムxxx_を指すようにしました.viewWillAppearの実装.これにより、UIViewControllerとそのサブクラスのオブジェクトがviewWillAppearを呼び出すと、ログ情報が印刷されます.
    上記の例はmethod swizzlingを用いてクラスに新しい操作を注入することをよく示している.もちろん、method swizzzlingを使用できるシーンはたくさんありますが、ここでは例を挙げません.ここではmethod swizzlingを使用する際に注意すべき問題について説明します.

    Swizzlingは常に+loadで実行されるはずです


    Objective-Cでは、実行時に各クラスの2つのメソッドが自動的に呼び出されます.+loadはクラスの初期ロード時に呼び出され、+initializeはクラスのクラスメソッドまたはインスタンスメソッドを最初に呼び出す前に呼び出されます.この2つの方法はオプションであり、実装された場合にのみ呼び出されます.method swizzlingはクラスのグローバル状態に影響するため、同時処理で競合することをできるだけ避ける必要があります.+loadは、クラスの初期化中にロードされることを保証し、このようなアプリケーションレベルを変更する動作の一貫性を保証することができる.対照的に、+initializeは実行時にこの保証を提供しません.実際には、アプリケーションでこのクラスにメッセージを送信していない場合、呼び出されない可能性があります.

    セレクタ、メソッド、実装


    Objective-Cでは、セレクタ(selector)、メソッド(method)、およびインプリメンテーション(implementation)は、通常、メッセージ送信のプロセス記述で使用されることが多いが、実行時の特殊な点である.
    以下に、Objective-C Runtime Referenceのいくつかの用語について説明します.
    Selector(typedef struct objc_selector*SEL):実行時にメソッドを表す名前.メソッドセレクタは、Objective-Cの実行時に登録されたC文字列です.セレクタはコンパイラによって生成され、クラスがロードされたときに実行時に自動的にマッピングされます.Method(typedef struct objc_method Method Method Method Method):クラス定義でメソッドを表すタイプImplementation(typedef id(IMP)(id,SEL,...):これは、メソッド実装関数の開始位置を指すポインタタイプです.この関数は、現在のCPUアーキテクチャに実装されている標準C呼び出し仕様を使用する.各パラメータはオブジェクト自体を指すポインタ(self)であり、2番目のパラメータはメソッドセレクタである.次に、メソッドの実際のパラメータです.これらの用語間の関係を理解する最善の方法は、1つのクラスが実行時に受信可能なメッセージの分割発表を維持することである.サブプレゼンテーションの各エントリは、keyがインプリメンテーション(IMP)、すなわち下位C関数へのポインタに対応する特定の名前、すなわちセレクタ(SEL)である方法(Method)である.
    swizzleの1つの方法のために,1つの方法の既存のセレクタを異なる実装にマッピングし,このセレクタに対応する元の実装を新しいセレクタに関連付けることができる.

    呼び出し_cmd


    振り返って、前の新しい方法の実装コードを見てみましょう.
    - (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
    }
    

    どのようにして無限の循環を招くように見えますか.しかし、驚くべきことに、このような状況は現れなかった.swizzlingでは、メソッドの[self xxx_viewWillAppear:animated]がUIViewControllerクラスの-viewWillAppear:に再指定されています.この場合、無限ループは発生しません.ただし、[self viewWillAppear:animated]を呼び出すと、このメソッドの実装は実行時にxxx_と再指定されているため、無限ループが発生します.viewWillAppear:はい.