ObjcRuntimeのいくつかの妙用について話します(class_addMethod,class_replaceMe

5115 ワード

前言: 今日お話しする知識点を陳列します:classaddMethod,classreplaceMethod,methodgetImplementation,objectgetClass
に関する知識 --categoryを使用して,Runtimeにより自己の関数で元の関数を入れ替える --ocのmessage forwarding --Runtimeを使用してクラスに元にないメソッドを追加 --なぜcategoryに書き換えないのか
注記: この文章の中の技術の参考はもちろん四方八方から来て、異なる時期から来て、弟はただ総括をして、悪いところがあってみんなの指導を歓迎します
まず一つのシーンの問題から持ち出しましょう.卒業設計の時、弟はipadアプリを作って、後になってから回転スクリーンを加えることにしました.100以上のファイルを見ています.20以上のページはもう少しで血を吐かないところでした.ハハ、コントロールごとに修正する方法は不可能です.強迫症も親を作りたくないので、これらのコントロールのviewWillAppearとw i l n i m ateRotationToInterfaceOrientation:duration:を一度に置き換えることにしました. まずcategoryがclassaddMethodとclassreplaceMethodを用いてシステムライブラリを置き換える方法を見てみましょう
#import "NSObject+Swizzle.h"@implementation NSObject (Swizzle)+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {

    Method originMethod = class_getInstanceMethod(self, origSel);
    Method newMethod = class_getInstanceMethod(self, aftSel);    if(originMethod && newMethod) {//    Method    
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {            //       
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }        return YES;
    }    return NO;
}@end

1.2つのパラメータ、元のメソッド選択子、新しいメソッド選択子を入力し、classgetInstanceMethod()で対応するMethodを取得します. 2.classaddMethodは、実装に対して言う、本来操作されるClassに存在しないnewMethodの実装を操作されるClassに追加し、その選択子としてorigSelを用いる(パラメータのselfは操作されるClassであることに注意し、ここでは類方法であることを忘れない). 3.classreplaceMethod,addMethodが成功裏に完了した後,パラメータから分かるように,methodgetImplaementation(roiginMethod)の選択子を入れ替え,元の方法の実現したSELを新しい方法のSEL:aftSel,okの目的を達成した.考えてみれば、今は古い方法SELで呼び出すと、新しい方法のIMPが実現し、新しい方法のSELで呼び出すと、古い方法のIMPが実現し、理一理の考えが続きます. 今回はNSStringをキャリアとしてプレゼンテーションしていきましょう.
#import "MyString.h"#import "NSObject+Swizzle.h"@implementation MyString+ (void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{
        Class clazz = object_getClass((id)self);
        [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
    });
}

+ (BOOL)myResolveInstanceMethod:(SEL)sel {    if(! [self myResolveInstanceMethod:sel]) {        NSString *selString = NSStringFromSelector(sel);        if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
            class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");            return YES;
        }else {            return NO;
        }
    }    return YES;
}

- (void)dynamicMethodIMP {    NSLog(@"         ");
}@end

1.まずここでresolveInstanceMethodについてお話しします.知らない人はocのmessage forwardingを補うことができます.つまり、実行時にオブジェクトが見つからない方法を呼び出したときにシステムが探すメカニズムです.この方法は最初に行った場所です.ここでruntimeに方法を追加することができます.はい、まずこの方法をハイジャックしなければなりません.私たち自身のことをして、さっきcategoryにカプセル化されたswizzleMethod:withMethod:------この時友達が疑問を持っていました.私たちはこの方法を書き直して自分のことをすることができます.実はできません.categoryで既存の方法を書き直すと警告があります.Category is implementing a method which will also be implemented by its primary classこのやり方は提唱されていません!---------categoryはサブクラスの代わりに使用できません.サブクラスのようにsuperによって親クラスを呼び出す方法では実現できません.categoryで現在のクラスのメソッドを上書きすると、この現在のクラスの元のメソッドが実装され、実行されません.これはいくつかのメソッドで致命的です(ここでは、現在のメソッドで実行されてからcategoryで実行される特例+(void)loadを示します).2つのcategoryが同じメソッドを書き換えると、どの優先度が高いかは制御できず、継承による書き換えが提唱されてきた. 2.objectgetClassは現在のMyStringのClassを手に入れて、さっきcategoryにカプセル化されたswizzleMethod:withMethod:を呼び出して、私たちのmyResolveInstanceMethod:で原生のものを置き換えて、よし、今私たちが実行時に存在しない方法を呼び出したら、システムは私たちのmyResolveInstanceMethodを呼び出します:、はい、疑う必要はありません. 3.今myResolveInstanceMethodを見てみましょう.myResolveInstanceMethodをもう一度呼び出します.原生的な方法の実現に変わりました、ok.if(![self myResolveInstanceMethod:sel])は、オリジナルメソッドを呼び出す実装であり、一度に入力されたメソッドが存在するかどうかを検出し、まだ存在しない場合はclassaddMethod操作を行い、このような対応メソッドを追加し、return YES、このメソッドはシステムに呼び出され、OK、目的を達成する. class_addMethodパラメータの意味
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

順番に、クラス--選択サブ--インプリメンテーション--メソッドの戻り値とパラメータ資料です. vは戻り値voidを表し、@はidタイプオブジェクトを表し、:選択子を表す. why? 実はそれぞれのocメソッドには2つの暗黙的なパラメータ(id self,SEL_cmd)があり、C言語関数に2つのパラメータを加えたocメソッドとも言える.
最後に私たちの仕事の収穫を見てみましょう.
NSLog(@"begin test");  
//------------------------------------------------

    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">  
//------------------------------------------------
    NSLog(@"finish test");

-----Log:2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683]            
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683]            
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683]            
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test