美文网首页
Method-Swizzling

Method-Swizzling

作者: 谌文 | 来源:发表于2022-07-29 23:11 被阅读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

    • [s personInstanceMethod];中不报错是因为 student中的imp交换成了lg_studentInstanceMethod,而LGStudent中有这个方法,所以不会报错

    • 崩溃的点在于[p personInstanceMethod];,其本质原因:LGStudent的中进行了方法交换,将person中imp 交换成了 LGStudent中的lg_studentInstanceMethod,然后需要去LGPerson中的找lg_studentInstanceMethod,但是LGPerson中没有lg_studentInstanceMethod方法,即相关的imp找不到,所以就崩溃了

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

    相关文章

      网友评论

          本文标题:Method-Swizzling

          本文链接:https://www.haomeiwen.com/subject/troqwrtx.html