runtime方法交换 为啥要先class_addMethod呢

方法交换
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector){
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

//底层调用了class_addMethod 最有一个参数yes首先先查看本类是不是有原始方法,如果没有创建一个方法列表并且把方法属性添加进去,后边有详细说明
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

if (didAddMethod) { 
 //注意 class_replaceMethod里 方法也调用了 class_addMethod方法 最有一个参数NO
    IMP rel =  class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
    method_exchangeImplementations(originalMethod, swizzledMethod);
 }
}

我们的前提是swizzledSelector这个SEL是真实存在于本class中的
1.originalSelector有可能存在父类(class_getInstanceMethod是按照继承链查找方法的)
2.如果originalSelector为父类方法,而本类没有,直接交换带来的后果就是影响了所有父类的事例对象的方法实现,后果就不可控了.
3.而如果originalSelector为父类方法,先调用class_addMethod将其添加到本类中,并且其方法实现为swizzledSelector的,这里相当于在添加的时候同步进行了方法实现交换.
4.如果添加成功,则说明originalSelector本来不存在于本class,那么剩下的就是将swizzledSelector所对应的方法实现替换成originalSelector的方法实现即可.
5.如果添加不成功,则说明originalSelector存在于本class,那么这时交换两个方法的实现是完全基于本类的,所以可控、安全.

runtime源码解释了上述说明:

class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
 ·····
 --重点--
 return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
 ·····
 --重点--
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
最终调用到:
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
  method_t *m = nil;
  重点在这!!!! class_getInstanceMethod方法会沿着继承链往上找, 
  如果本类没有,父类有,则找到的是父类方法
  while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
    cls = cls->superclass;
}
return m;
}
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;
// 只留核心代码
method_t *m;
// 查看本类是否有此方法,这里也是个重点,NoSuper也就是不会沿着继承链查找,只在本类进行
if ((m = getMethodNoSuper_nolock(cls, name))) {
    // already exists
    // 如果有,但是不替换
    if (!replace) {
        // 直接获取结果
        result = m->imp;
    } else {
        // 如果需要替换,则直接将实现覆盖
        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;
    
    //  对新的方法列表的添加做准备
    //  1, 注册方法名
    //  2, 对方法列表进行排序
    //  3, 标记此方法列表唯一,并且有序
    prepareMethodLists(cls, &newlist, 1, NO, NO);
    
    // 添加到现有的方法列表之后
    cls->data()->methods.attachLists(&newlist, 1);
    
    //  刷新缓存
    flushCaches(cls);

    result = nil;
}

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

mutex_locker_t lock(runtimeLock);
// 它的核心就是调用了addMethod 最后一个参数“YES”的意思如果class存在SEL为name的方法,则替换它的实现.这个过程在Step1的源码解析中有.
return addMethod(cls, name, imp, types ?: "", YES);
}
method_exchangeImplementations
 void method_exchangeImplementations(Method m1, Method m2)
{
// 判空 
if (!m1  ||  !m2) return;

// 上锁
mutex_locker_t lock(runtimeLock);

// 经典指针指向交换
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;

// 修复其方法在外部已知的类的生成列表
// 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);

// 看方法名是根据方法变更 适配class 中 custom flag状态
// 内部方法进行了RR/AWZ/CORE 等方法的扫描
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
 static void
 adjustCustomFlagsForMethodChange(Class cls, method_t *meth)
{
objc::AWZScanner::scanChangedMethod(cls, meth);
objc::RRScanner::scanChangedMethod(cls, meth);
objc::CoreScanner::scanChangedMethod(cls, meth);
}

总结

1.class_addMethod的作用本质是防止产生父类和子类方法实现的交换,这样会打破父类原有的生态,导致不可预计的问题.
2.添加新方法的时候会创建新的方法列表,并且对其进行注册等准备工作,添加到现有的方法列表之后
3.交换方法实现,添加方法都会对缓存进行一次清空,这种动作是比较耗费资源的,如果缓存比较大,会大幅降低缓存命中,降低效率(class_addMethod 中 flushCaches(cls);)
4.方法交换后,runtime会遍历NSObject和元类,查看是否有和这两个方法相关联的地方,并进行setNSObjectSwizzled设置,标记本类中是否有Swizzled操作(adjustCustomFlagsForMethodChange中scanChangedMethod底层)objc源码
仅作为学习记录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。