Method-Swizzling

2022-07-29  本文已影响0人  谌文

1 . class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
会一直找(lookUpImpOrForward),经历快速慢速,一直往上找(父类),找SEL的IMP,有则返回Method,否则返回nil

2.class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)

找到当前类是否有SEL, 没有,则添加方法,返回true, 有则不会添加,返回false

  1. class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
    const char * _Nullable types)
    class_addMethod内部屌用的是old = _class_addMethod(cls, name, imp, types, NO);
    return !old;
    而 class_replaceMethod 内部屌用的是 return _class_addMethod(cls, name, imp, types, YES);

    if (replace) {
    method_setImplementation((Method)m, imp);
    }
    所以内部原理是class_addMethod逻辑的基础上多一个,查找到的话,会将SEL指向IMP

  2. void method_exchangeImplementations(Method m1_gen, Method m2_gen)

方法交换,内部会进行参数非空判断,参数有为空的话,不进行交换

只是简单的进行反复交换

void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
    IMP m1_imp;
    old_method *m1 = oldmethod(m1_gen);
    old_method *m2 = oldmethod(m2_gen);
    if (!m1  ||  !m2) return;

    impLock.lock();
    m1_imp = m1->method_imp;
    m1->method_imp = m2->method_imp;
    m2->method_imp = m1_imp;
    impLock.unlock();
}

问题1:

@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end

@implementation LGPerson
- (void)personInstanceMethod{
      NSLog(@"person对象方法:%s",__func__);
}

/// LGStudent继承LGPerson
@interface LGStudent : LGPerson
@end
@implementation LGStudent
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}

// personInstanceMethod 我需要父类的这个方法的一些东西
// 给你加一个personInstanceMethod 方法
// imp

// 是否递归
- (void)lg_studentInstanceMethod{
    [self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
    NSLog(@"LGStudent添加的lg对象方法:%s",__func__);
}
@end

///  Method-Swizzling
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    if (!cls) NSLog(@"传入的交换类不能为空");
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);
}

int main(int argc, char * argv[]) {
    // 黑魔法坑点一: 子类没有实现 - 父类实现
    LGStudent *s = [[LGStudent alloc] init];
    [s personInstanceMethod];
    
    // personInstanceMethod -> lg_studentInstanceMethod
   LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
}

问题描述:
父类: LGPerson SEL: personInstanceMethod 方法实现IMP:personInstanceMethod;
子类LGStudent继承SHPerson
此时子类LGStudent +load方法中 交换personInstanceMethod-> lg_studentInstanceMethod

运行结果是是什么?

子类正常
person对象方法:-[LGPerson personInstanceMethod]
LGStudent添加的lg对象方法:-[LGStudent lg_studentInstanceMethod]

父类奔溃
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGPerson lg_studentInstanceMethod]: unrecognized selector sent to instance 0x600002a4c350'
terminating with uncaught exception of type NSException

why

method_exchangeImplementations: 此方法在交换方法时先在当前类查找方法进行交换,没有则找父类

证明:源码

void method_exchangeImplementations(Method m1Signed, Method m2Signed)
{
    if (!m1Signed  ||  !m2Signed) return;

    method_t *m1 = _method_auth(m1Signed);
    method_t *m2 = _method_auth(m2Signed);

    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);
}

进入flushCaches

static void flushCaches(Class cls, const char *func, bool (^predicate)(Class))
{
    runtimeLock.assertLocked();
#if CONFIG_USE_CACHE_LOCK
    mutex_locker_t lock(cacheUpdateLock);
#endif

    const auto handler = ^(Class c) {
        if (predicate(c)) {
            c->cache.eraseNolock(func);
        }

        return true;
    };

    if (cls) {
        foreach_realized_class_and_subclass(cls, handler);
    } else {
        foreach_realized_class_and_metaclass(handler);
    }
}

进入
foreach_realized_class_and_subclass

static inline void
foreach_realized_class_and_subclass_2(Class top, unsigned &count,
                                      bool skip_metaclass,
                                      bool (^code)(Class) __attribute((noescape)))
{
    Class cls = top;

    runtimeLock.assertLocked();
    ASSERT(top);

    while (1) {
        if (--count == 0) {
            _objc_fatal("Memory corruption in class list.");
        }

        bool skip_subclasses;

        if (skip_metaclass && cls->isMetaClass()) {
            skip_subclasses = true;
        } else {
            skip_subclasses = !code(cls);
        }

        if (!skip_subclasses && cls->data()->firstSubclass) {
            cls = cls->data()->firstSubclass;
        } else {
            while (!cls->data()->nextSiblingClass  &&  cls != top) {
                cls = cls->getSuperclass();
                if (--count == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
            }
            if (cls == top) break;
            cls = cls->data()->nextSiblingClass;
        }
    }
}

查找父类
 cls = cls->getSuperclass();

解决方法

使用如下方法进行交换

+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    // oriSEL       personInstanceMethod
    // swizzledSEL  lg_studentInstanceMethod
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   
    // 尝试添加你要交换的方法 - lg_studentInstanceMethod, 根据源码得知,尝试查找 当前类是否有oriSEL方法,没有的话,class_addMethod则是成功的,还会把方法真的添加进入
    // SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    // 此时来交换就不会影响到父类的方法啦

    /**
     personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
     lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
     */
    
    if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        /// 这个方法会先试着交换自己本类的方法,没有则往父类查找交换,此时就会影响到父类方法啦
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

先尝试添加class_addMethod
成功则 class_replaceMethod,失败则method_exchangeImplementations

打印结果:

子类正常
person对象方法:-[LGPerson personInstanceMethod]
LGStudent添加的lg对象方法:-[LGStudent lg_studentInstanceMethod]

父类personInstanceMethod 方法不受影响
person对象方法:-[LGPerson personInstanceMethod]

能解决问题的原理
分析下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);
}

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;
/// 从当前类中查找是否存在SEL: name,本例子中是personInstanceMethod,
/// 在当前类中不存在(存在父类中), 不存在,则走入else中
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;

        /// 则进行方法添加,SEL:personInstanceMethod IMP: lg_studentInstanceMethod
/// 之后还会对方法进行排序, 则LGStudent则有个 SEL:personInstanceMethod IMP: lg_studentInstanceMethod
        addMethods_finish(cls, newlist);
        result = nil;
    }
    return result;
}


////. 查找当前类要交换的方法

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

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

class_addMethod 添加LGStudent- SEL:personInstanceMethod IMP: lg_studentInstanceMethod成功后
则进行 class_replaceMethod:(将SEL:lg_studentInstanceMethod又指向 IMP: personInstanceMethod)
此时就是 LGStudent中SEL:personInstanceMethod->IMP: lg_studentInstanceMethod, 子类LGStudent SEL:lg_studentInstanceMethod->父类的IMP: personInstanceMethod

class_replaceMethod流程:

IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

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

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;
/// 此时replace==YES;并且LGStudent之前class_addMethod已经添加personInstanceMethod啦,所有会进入到if中
所以
    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;
}

static IMP 
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
    runtimeLock.assertLocked();

    if (!m) return nil;
    if (!imp) return nil;

    IMP old = m->imp(false);
    SEL sel = m->name(); /// 发现此处class_replaceMethod和method_exchangeImplementations在此处的区别了吗。

///method_exchangeImplementations中
/**
    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();
原先: A1(SEL)->A2(IMP), B1(SEL)->B2(IMP), 
A1->B2,  B1->A2
*/

/// class_replaceMethod 是  
/// IMP old = m->imp(false);  SEL sel = m->name()     B1->A2
 
    m1->setImp(imp2);
    m2->setImp(imp1);


    // Cache updates are slow if cls is nil (i.e. unknown)
    // RR/AWZ updates are slow if cls is nil (i.e. unknown)
    // fixme build list of classes whose Methods are known externally?

///.此时就是将SEL-IMP等同与
    m->setImp(imp);  LGStudent的SEL: lg_studentInstanceMethod->父类的IMP: personInstanceMethod
    flushCaches(cls, __func__, [sel, old](Class c){
        return c->cache.shouldFlush(sel, old);
    });

    adjustCustomFlagsForMethodChange(cls, m);

    return old;
}

问题2:

子类没有实现,父类也没有实现,下面的调用有什么问题?

@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LGPerson
@end

/// LGStudent继承LGPerson
@interface LGStudent : LGPerson
@end
@implementation LGStudent

@end

int main(int argc, char * argv[]) {
    // 黑魔法坑点一: 子类,父类都没有实现
    LGStudent *s = [[LGStudent alloc] init];
    [s personInstanceMethod];
    
    // personInstanceMethod -> lg_studentInstanceMethod
   LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
}
- (void)lg_studentInstanceMethod{
    /// 递归啦
    [self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
    NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}

原因是 栈溢出,递归死循环了,那么为什么会发生递归呢?----主要是因为

/// cls: LGStudent. oriSEL: personInstanceMethod; 没有实现所以oriMethod==nil
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);

    // 尝试添加你要交换的方法 - lg_studentInstanceMethod, 根据源码得知,尝试查找 当前类是否有oriSEL方法,没有的话,class_addMethod则是成功的,还会把方法真的添加进入
    // SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    // 此时来交换就不会影响到父类的方法啦

    /**
     personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
     lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
     */
    
    if (success) {// 此时LGStuden添加成功了SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
/// 然后执行class_replaceMethod时: oriMethod为nil 所以失败啦, 
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        /// 这个方法会先试着交换自己本类的方法,没有则往父类查找交换,此时就会影响到父类方法啦
        method_exchangeImplementations(oriMethod, swiMethod);
    }

解决方案: Method oriMethod = class_getInstanceMethod(cls, oriSEL);需要进行非空判断下

+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!oriMethod) {
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
    // 交换自己没有实现的方法:
    //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL
    //oriSEL:personInstanceMethod

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }

}
上一篇下一篇

猜你喜欢

热点阅读