【移行】MethodSwizzling(二)

5846 ワード

codingのプレゼンテーション機能は使えず、元のブログにはアクセスできなくなった.いっそすべてのブログを移転して、この文章は古い文章で、みんなが後で私のブログを見に来ることを歓迎します
前のブログではMethod Swizzzlingを簡単に紹介しましたが、前の文章を見てMethod Swizzling is so easyと思うかもしれませんが、そんなに簡単ではありません.細部に注意が必要だ!!!

詳細


ARC以前は、メモリ管理に悩まされていましたが、なぜか解放され、どこで解放されたのか分からないオブジェクトもありました.どのオブジェクトdeallocでもlogを印刷し、logを見るだけでどこで解放されたかがわかります.オブジェクトのクラスが多く、deallocを書き換える作業量が多いため、Swizzlingを使うことにしました.
上コード~~~
@implementation NSObject(Swizzling)
void methodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
    Method originMethod = class_getInstanceMethod(class, originSel);
    Method overrideMethod = class_getInstanceMethod(class, overrideSel);
    
    if (class_addMethod(class,
                        originSel,
                        method_getImplementation(overrideMethod),
                        method_getTypeEncoding(originMethod)))
    {
        /** case1:NSMutableDictionary -setObject:forKey:  */
        class_replaceMethod(class,
                            overrideSel,
                            method_getImplementation(originMethod),
                            method_getTypeEncoding(originMethod));
    }else{
        /** case2:NSMutableDictionary -setObject:forKey:    */
        method_exchangeImplementations(originMethod, overrideMethod);
    }
}

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        methodSwizzling([NSObject class], @selector(dealloc), @selector(swzzling_dealloc));
    });
}

- (void)swzzling_dealloc
{
    printf(" ");
    [self swzzling_dealloc];
}
@end

@selector(dealloc)はARCでコンパイラがエラーを報告するため、例は非ARCで実行する必要があります.
例は簡単で幼稚で、現実にはこのような需要はないかもしれませんが、これは重点ではありません.重点は......

1. +load VS +initialize


なぜSwizzlingのコードを+loadに入れるのか考えたことがある人はいませんか?why?
=>Swizzlingのコードはメソッドの実行前にしなければならないので、そうでなければメソッドはすべて実行され、Swizzlingでは意味がありません.+loadメソッドはmain関数の前に呼び出される(classがruntimeに追加されたときに呼び出される)ので、メソッドの実行前にSwizzlingが保証されます.
+initializeメソッドは、メソッドの実行前に呼び出すことも保証されますが、Swizzlingのコードをここに置くことができますか?
=>initializeの中に入れると問題ないことが多いですが、リスクがあります.+initializeメソッドはClassが最初のメッセージを受信する前に実行されることを知っています.すなわち、継承関係のある複数のクラスがあり、彼らの+initializeメソッドの実行順序は、特定のコードに依存し、どのクラスが最初のメッセージを受信し、どのクラスの+initializeメソッドが先に実行されるかに依存します.これらのクラスが同じMethodに対してSwizzlingを実行している場合、彼らの行動の不確実性を招き、どのSwizzlingが先に実行されたのか分からないため、隠れた発見しにくいバグが発生する可能性があります.+load実行の順序は決定されます.親クラスの+loadは、子クラスが実行される前に実行されます.

2. dispatch_once


複数のスレッドが同じセグメントのSwizzlingのコードを同時に実行すると、混乱をもたらし、望ましくない結果をもたらす可能性があることが明らかになったので、一般的にSwizzlingのコードはdispatch_に置く必要があります.onceではありますが、システムは+loadメソッドが同時に複数回実行されないことを保証しているので、+loadにはdispatch_を追加しません.onceもあまり影響はありませんが、良いコードシステムを身につけるためにdispatch_を加えます.onceが一番

3. Swizzling Class Method


従来、InstanceMethod(インスタンスメソッド)のみをSwizzlingしてきましたが、Class Method(クラスメソッド)をSwizzlingする必要がある場合はどうすればいいのでしょうか.まず、答えを見てみましょう.
void classMethodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
    Class metalClass = object_getClass(class);
    methodSwizzling(metalClass, originSel, overrideSel);
}

従来のClassをmetalClassに置き換えるだけでClassMethodをSwizzlingできることがわかります.but why? これを説明するには、まずClassを理解する必要があります.
Class関係図
Instance,Class,MetalClassの関係を図に示しますが、次の2点について説明します.
  • InstanceのclassポインタはClassを指し、ClassのclassポインタはMetalClassを指す.簡単に言えば、MetalClassのインスタンスはClassであり、Classのインスタンスは本物のインスタンスInstanceであると理解できる.
  • MetalClassの構造はClassの構造と全く同じで、彼らの違いはClassにインスタンスメソッドを保存し、MetalClassにクラスメソッドを保存することだけです.

  • まずobject_getClass()メソッドはインスタンスオブジェクトを入力し、インスタンスオブジェクトのClassを返します.1から分かるように、classが入力され、MetalClassが返される.
    Classにはインスタンスメソッドのみが格納されているため、クラスメソッドをSwizzlingするにはMetalClass上で行う必要があり、MetalClassとClassの構造は全く同じであるため、元のClassをmetalClassに置き換えるだけでClassMethodをSwizzlingすることができる.

    Danger


    Method Swizzzlingにはリスクがあることはよく知られているかもしれませんが、具体的なリスクがどこにあるかは、あまり明確ではないかもしれません.私はいくつかの資料を探して、いくつかのSwizzlingのリスクがある場所を集めて、もしみんなが他のリスクを発見したら、私に連絡してください.

    1.Refused by AppStore


    危険係数:★★★★★遭遇確率:★☆☆☆☆かつてSwizzzlingシステムAPIでAppStoreに拒否されたことがありましたが、長い間ネットで調べていましたが、この事件は一度しか発生せず、後の人が同じことをしても拒否されなかったことも、これも審査の人と関係があるはずです.一般的には断られないはずなので、この危険係数は極めて高いが、遭遇確率も非常に低い.事件の詳細はこちら

    2.Class Cluster


    危険係数:★★★★☆遭遇確率:★★★☆☆類族の概念は前ブログで述べた.簡単に言えば、クラスは表面的にはNSString(NSNumber,NSArray,NSDictionaryなど)のようなクラスを使っているようですが、実際には彼らのサブクラスを使っています.NSCFConstantString,NSPathStore 2,NSBigMutablesStringなど,これらのサブクラスは公開されていないため,いったいどのくらいのサブクラスがあるのか,後でサブクラスを増やすことができるのか分からないため,Swizzlingの場合はすべてのサブクラスを上書きすることができず,NSStringを使用する方法がSwizzlingに過ぎた場合もあり,Swizzzlingを使用していない方法が呼び出される場合もある.このような場合は、Method Swizzlingの使用を放棄することをお勧めします.

    3. Swizzling changes the method's arguments


    危険率:★☆☆遭遇確率:★★★★☆Swizzlingメソッドのパラメータを変更しました.メソッドを呼び出すと[obj test]システムはそれをメッセージ送信objc_に変換することを知っています.msgSend(obj,@selector(test))は、objとSELをメソッドに転送し、メソッドでは、_cmdを介して転送されたSELを取得することができ、従来のSwizzlingメソッドSwizzlingを介して、転送元の関数のSELが変更され、元の関数で_cmdを使用するとエラーが発生する可能性があります.幸い、このリスクは回避できますが、以下の修正でこのリスクを回避できます.
    - (void)swzzling_dealloc
    {
        printf(" ");
    //    [self swzzling_dealloc];
        IMP originImp = class_getMethodImplementation([self class], @selector(swzzling_dealloc));
        originImp(self,_cmd);
    }
    

    IMPを直接呼び出し、正しい_cmdを入力すればよい.
    IMPを直接呼び出す書き方は乱暴で、数日後にはそれを一つの方法にカプセル化する方法を検討してから更新することができます.IMPが入力するパラメータは可変長パラメータであるため、カプセル化する方法が入力するパラメータも可変長パラメータでなければなりませんが、現在は可変長パラメータを別の関数に渡すことができないので、この場所は一時的に一つの方法にカプセル化できません.他に何かできることがあれば、連絡してください.

    4.others


    ここには他のリスクもリストされています.念茜大神はここで彼を翻訳しました.私はここで贅沢に言いません.興味のある方はご覧ください.中にはもう一つのMethod Swizzlingの方法も提供されています.同じように3つの問題を解決することができます.

    リファレンス


    Method Swizzling in nshipster
    Method Swizzling in codeproject
    What are the Dangers of Method Swizzling in Objective C?
    Objective-Cのhookスキーム(一):Method Swizzling