RunTime源码阅读(十一)之方法添加原理

1.Runtime交换

Class class = object_getClass((id)self);//元类
    
    SEL originalSelector = @selector(testExchangeClassMethod2);
    SEL swizzledSelector = @selector(testExchangeClassMethodAfter2);

    Method originalMethod = class_getClassMethod(self, originalSelector);
    Method swizzledMethod = class_getClassMethod(self, swizzledSelector);
    
    //class_addMethod:查询originalSelector是否存在
    //存在为NO;不存在为YES,并且会当前类动态添加originalSelector
    //后两个参数没有用到
    BOOL isAddOriginalMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    //class_addMethod为类添加了未实现的方法,但是originalMethod仍然是空
    //重新获取就有值
    Method originalMethod1 = class_getClassMethod(self, originalSelector);

    if (isAddOriginalMethod) {
        //动态添加originalMethod,并把imp指针赋值给swizzledSelector达到交换
        //直接替换也没有毛病
        //相当于调用了class_addMethod方法,会检测swizzledSelector是否存在
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        //如果originalMethod未实现,可以重新获取originalMethod1
        method_exchangeImplementations(originalMethod1, swizzledMethod);//有一个方法为空,不会交换
    }

问题:method_exchangeImplementations与class_replaceMethod交换有什么区别?
method_exchangeImplementations原理

2.原理

2.1class_getClassMethod

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
//getMeta对isa查找的封装,如果是元类返回self,如果是类则返回isa。与self.class查找有点像
    return class_getInstanceMethod(cls->getMeta(), sel);//cls->getMeta()不是类对象,是元类
}

由于cls是类,方法存储在元类中,需要通过isa找到元类中中的sel。

2.2class_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;
    if ((m = getMethodNoSuper_nolock(cls, name))) {//SEL name已经实现
        // already exists
        if (!replace) {
            result = m->imp;//name方法存在,返回对应的imp
        } else {
            result = _method_setImplementation(cls, m, imp);//把m.imp=imp
        }
    } else {//SEL name没有实现,imp和name赋给newlist->first
        // 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);//添加到data()->methods中
        flushCaches(cls);//添加完成填充到缓存

        result = nil;
    }

    return result;
}
  1. getMethodNoSuper_nolock(cls, name),查找cls中的方法name,如果找到返回m.
  2. 如果m没有,即cls中不存在name方法
    分配一个method_list_t结构体,并赋值name,imp,type第一个;
    修正方法排序;
    添加到cls->data()->methods中;
    填充到缓存;
    即生成一个新的newlist,并追加到原有的方法列表中。
  3. 如果m不为空
    如果replace为NO,即不交换方法,不做处理;如果replace为yes,把m.imp=imp.

没有就新生成一个,追加到原来的方法列表中,如果有且replace为yes,把imp给name对应的方法。
isAddOriginalMethod如果为NO,交换的方法肯定存在,用method_exchangeImplementations即可;如果为yes,即新添加了方法,走class_replaceMethod方法。

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

本质也是调用了addMethod方法。

小结:
1.如果两个方法都明确确定存在,使用method_exchangeImplementations即可
2.如果原方法不确定是否存在,可用class_addMethod判断
3.如果交换后的方法不确定是否存在,可用class_replaceMethod
4.如果原方法与交换后的方法都不确定是否存在,可用class_addMethod与
class_replaceMethod结合,不过这种应用场景不明确。

原理这样,但什么方法都不存在,还交换什么,大部分直接使用method_exchangeImplementations即可;如果遇到看不到源码,不明确的,可以用class_replaceMethod。

如果方法不存在,class_addMethod之后,再Method originalMethod1 = class_getClassMethod(self, originalSelector);获取之后,originalMethod1就存在了,再使用method_exchangeImplementations交换也是可以的。

你可能感兴趣的:(RunTime源码阅读(十一)之方法添加原理)