runtime07 - methodSwizzle

419 阅读3分钟

方法交换的原理

在本类开始, 顺着继承链, 在方法列表中, 查找要交换的Method, 查找到后交换Methodimp指针

方法交换注意事项

在交换方法时, 要注意是否会影响到父类的方法实现.

方法交换模板

基础版

// 如果originalSelector或者swizzledSelector对应的Method, 不在自己的方法列表中, 会导致父类的方法收到影响
void swizzle1(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

大众版 (RN / AFN都用的这个)

// 如果swizzledSelector对应的Method, 不在自己的方法列表中, 会导致父类的方法收到影响
void swizzle2(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
     BOOL didAddMethod =
     class_addMethod(aClass,
                     originalSelector,
                     method_getImplementation(swizzledMethod),
                     method_getTypeEncoding(swizzledMethod));

     if (didAddMethod) {
         class_replaceMethod(aClass,
                                            swizzledSelector,
                                            method_getImplementation(originalMethod),
                                            method_getTypeEncoding(originalMethod));
     } else {
         method_exchangeImplementations(originalMethod, swizzledMethod);
     }
}

建议版

void swizzle(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
    class_addMethod(aClass,
                    originalSelector,
                    method_getImplementation(originalMethod),
                    method_getTypeEncoding(originalMethod));
    class_addMethod(aClass,
                    swizzledSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    method_exchangeImplementations(class_getInstanceMethod(aClass, originalSelector), class_getInstanceMethod(aClass, swizzledSelector));
}

方法交换应用

  1. 页面的pv/pd 统计 (方法交换 viewDidAppear / viewDidDisappear)
  2. 防止按钮短时间点击多次 (交换sendAction:to:forEvent:)

DEMO

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Person: NSObject
@property (nonatomic, assign) int age;
- (void)personInstanceMethod;
- (void)personInstanceMethod2;
@end

@implementation Person
- (void)personInstanceMethod {
    NSLog(@"personInstanceMethod %d", self.age);
}
- (void)personInstanceMethod2 {
    NSLog(@"personInstanceMethod2 %d", self.age);
}
@end

@interface Student : Person
@property (nonatomic, assign) int score;
- (void)studentInstanceMethod;
@end

@implementation Student

// 如果originalSelector或者swizzledSelector对应的Method, 不在自己的方法列表中, 会导致父类的方法收到影响
void swizzle1(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
// 如果swizzledSelector对应的Method, 不在自己的方法列表中, 会导致父类的方法收到影响
void swizzle2(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
     BOOL didAddMethod =
     class_addMethod(aClass,
                     originalSelector,
                     method_getImplementation(swizzledMethod),
                     method_getTypeEncoding(swizzledMethod));

     if (didAddMethod) {
         class_replaceMethod(aClass,
                                            swizzledSelector,
                                            method_getImplementation(originalMethod),
                                            method_getTypeEncoding(originalMethod));
     } else {
         method_exchangeImplementations(originalMethod, swizzledMethod);
     }
}

void swizzle3(Class aClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    if(originalMethod == nil || swizzledMethod == nil) {
        return;
    }
    class_addMethod(aClass,
                    originalSelector,
                    method_getImplementation(originalMethod),
                    method_getTypeEncoding(originalMethod));
    class_addMethod(aClass,
                    swizzledSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    method_exchangeImplementations(class_getInstanceMethod(aClass, originalSelector), class_getInstanceMethod(aClass, swizzledSelector));
}

+ (void)load {

    static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
//          swizzle1(self, @selector(studentInstanceMethod), @selector(personInstanceMethod2));
//          swizzle2(self, @selector(studentInstanceMethod), @selector(personInstanceMethod2));
          swizzle3(self, @selector(studentInstanceMethod), @selector(personInstanceMethod2));
      });
}

- (void)studentInstanceMethod {
    NSLog(@"studentInstanceMethod %d",self.score);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

        [[Person new] personInstanceMethod];
        [[Person new] personInstanceMethod2];
        [[Student new] studentInstanceMethod];
    }
    return 0;
}

源码:

查找方法

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;

    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->getSuperclass();
    }

    return m;
}

方法添加

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertLocked();

    checkIsKnownClass(cls);
    
    ASSERT(types);
    ASSERT(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp(false);
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
        newlist->count = 1;
        auto &first = newlist->begin()->big();
        first.name = name;
        first.types = strdupIfMutable(types);
        first.imp = imp;

        addMethods_finish(cls, newlist);
        result = nil;
    }

    return result;
}

方法交换

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();

    m1->setImp(imp2);
    m2->setImp(imp1);


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}