Objective-C Method Swizzling

Method Swizzling已經被聊爛了,都知道這是Objective-C的黑魔法,能夠交換兩個方法的實現。今天我也來聊一下Method Swizzling。objective-c

使用方法

咱們先貼上這一大把代碼吧ui

@interface UIViewController (Swizzling)

@end

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(swizzling_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)swizzling_viewWillAppear:(BOOL)animated {
    [self swizzling_viewWillAppear:animated];
    NSLog(@"==%@",NSStringFromClass([self class]));
}

@end

好的,上面就是Method Swizzling的使用方法,將方法- (void)swizzling_viewWillAppear:(BOOL)animated和系統級方法- (void)viewWillAppear:(BOOL)animated交換。經常使用的場景就是埋點,這個咱就不細說了。spa

這裏咱們說一個點就是實現代碼的位置問題。code

咱們的交換代碼必須只能調用一次,若是執行屢次的,那不就把交換的實現又換回來了嗎,因此咱們必須找一個只會調用一次的地方來寫實現交換的代碼。blog

咱們都知道+load會在加載類的時候執行,並且只執行一次,可是爲了進一步保障他只能執行一次,咱們使用了dispatch_once(由於會有人手動調用+load方法)。繼承

此外+load方法還有一個很是重要的特性,那就是子類、父類和分類中的+load方法的實現是被區別對待的。換句話說在 Objective-C runtime 自動調用+load方法時,分類中的+load方法並不會對主類中的+load方法形成覆蓋。get

Method Swizzling 實現分析

在取到了SEL和Method以後,執行了下面這句代碼源碼

BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

而後根據success來作不一樣的處理。io

這裏咱們先說結論,就拿咱們上面的代碼做爲例子。table

若是咱們的主類,也就是UIViewController(咱們是給UIViewController建立的Category)實現了viewWillAppear:方法,success爲NO,若是沒有實現,則爲YES。在咱們的例子中的UIViewController確定是實現了viewWillAppear:方法的,因此success確定爲NO。

若是咱們這裏給一個自定義的VC建立Category實現Swizzling,而且VC沒有顯式的實現viewWillAppear:(繼承父類的),這時success就是YES了。

你們能夠本身建立不一樣類和類別的試一試。

class_addMethod

咱們經過runtime的源碼來看一下class_addMethod內部作了什麼操做。

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

class_addMethod返回值是對addMethod返回值取反。這個地方稍微有點彆扭。咱們能夠看一下addMethod方法,返回值是IMP,因此說:

主類實現了被Swizzling的方法,success爲NO,即class_addMethod返回NO,addMethod返回值不爲空。

主類未實現了被Swizzling的方法,success爲YES,即class_addMethod返回YES,addMethod返回值爲空。

// addMethod方法的主要代碼

method_t *m;

if ((m = getMethodNoSuper_nolock(cls, name))) {
    // already exists
    if (!replace) {//replace==NO (class_addMethod)
        result = m->imp;
    } else {//(class_replaceMethod)
        result = _method_setImplementation(cls, m, imp);
    }
}
    
else {
    // fixme optimize
    method_list_t *newlist;
    newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
    newlist->entsizeAndFlags = 
        (uint32_t)sizeof(method_t) | fixed_up_method_list;
    newlist->count = 1;
    newlist->first.name = name;
    newlist->first.types = strdupIfMutable(types);
    newlist->first.imp = imp;

    prepareMethodLists(cls, &newlist, 1, NO, NO);
    cls->data()->methods.attachLists(&newlist, 1);
    flushCaches(cls);

    result = nil;
}

咱們結合源碼來梳理上面提到的兩種狀況。

首先method_t其實就是一個儲存方法的細節的結構體。
經過m = getMethodNoSuper_nolock(cls, name)查找類中對應的方法的信息,包括方法名,實現等等。

  1. 若是主類中實現了被swizzling的方法

    調用m = getMethodNoSuper_nolock(cls, name)查找,這裏應該是能夠找到的,class_addMethod方法中調用addMethod的時候relpace傳的是NO,因此會執行result = m->imp;,其實就是給result賦了個值,讓他不爲空。class_addMethod的返回值是addMethod返回值取反,因此此時class_addMethod返回爲NO。

    繼續往下執行method_exchangeImplementations(originalMethod, swizzledMethod);,這就很好理解了,就是直接把須要交換的兩個方法的實現直接交換。

  2. 若是主類中沒有實現被swizzling的方法

    getMethodNoSuper_nolock找不到,m仍是爲空,因此會執行else下面的代碼,這裏面的代碼其實很明顯,就是動態的爲咱們的主類建立實現了須要被swizzling的方法,固然了,由於此時咱們傳入class_addMethod方法的sel是須要被swizzling的方法,可是實現已是傳了須要替換後的實現,因此執行完else裏面的代碼以後,咱們的須要被swizzling的方法的實現,已經指向了替換後的實現,也就是viewWillAppear:的IMP其實此時已經指向了swizzling_viewWillAppear:的IMP。

    最後result置爲nil,因此class_addMethod返回值就是YES,success爲YES。

    最後再執行class_replaceMethod方法。

    從源碼中來看,class_replaceMethodclass_addMethod都是調用了addMethod,但有兩點不一樣,一來是class_replaceMethod在調用addMethod時,參數replace傳YES,再者就是由於class_replaceMethod的返回值是一個IMP,因此和addMethod是一致的,直接return了addMethod的返回值。

    在經過class_replaceMethod調用addMethod的時候,雖然咱們主類以前沒實現要被swizzling的方法,可是在上一步中,咱們已經動態的添加了,因此此時getMethodNoSuper_nolock是能找到的。

    最終執行result = _method_setImplementation(cls, m, imp);_method_setImplementation內部實現很簡單,先用一個old記錄下m->imp 而後再把m->imp設置爲傳入的imp,隨後返回old,其實也就是返回了沒交換前的m->imp。

    這樣咱們就經過_method_setImplementation方法把咱們的swizzling_viewWillAppear:的IMP指向了viewWillAppear:的IMP。完成了viewWillAppear:swizzling_viewWillAppear:兩個方法實現的交換。


參考資料

http://blog.leichunfeng.com/b...

相關文章
相關標籤/搜索