iOS

iOS-底层原理19:method-swizzling涉及的相关

2020-12-15  本文已影响0人  AcmenL

在上一篇文章iOS-底层原理18:Method-Swizzling 方法交换 分析了runtime方法交换,但是对于其中一些API了解并不十分清楚,这篇文章将通过源码来深入理解这些API。

这里需要用到objc4源码

method-swizzling涉及的相关API

方法名 作用
class_getInstanceMethod 获取实例方法
class_getClassMethod 获取类方法
method_getImplementation 获取一个方法的实现
method_setImplementation 设置一个方法的实现
method_getTypeEncoding 获取方法实现的编码类型
class_addMethod 添加方法实现
class_replaceMethod 用一个方法的实现,替换另一个方法的实现,即aIMP -> bIMP,但是bIMP !--> aIMP
method_exchangeImplementations 交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP

class_getInstanceMethod

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

可以看到一个熟悉的函数lookUpImpOrForward,在iOS-底层原理11:消息流程分析之慢速查找中介绍过,这个慢速查找的核心方法

class_getClassMethod

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

类对象的类方法实际上是元类对象的实例方法,这类直接查找元类的实例方法

method_getImplementation

IMP 
method_getImplementation(Method m)
{
    return m ? m->imp : nil;
}

直接从方法结构体中获取imp变量

method_setImplementation

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;
    m->imp = imp;

    // 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?

    flushCaches(cls);

    adjustCustomFlagsForMethodChange(cls, m);

    return old;
}

将方法mimp指向传进来的imp参数,返回的是方法m原来的imp

method_getTypeEncoding

const char *
method_getTypeEncoding(Method m)
{
    if (!m) return nil;
    return m->types;
}

直接从方法结构体中获取types变量

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

⬇️

/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        auto rwe = cls->data()->extAllocIfNeeded();

        // 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);
        rwe->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

通过调用getMethodNoSuper_nolock去类的方法列表中查找,找到就判断传进来的replace,如果为NO,就直接返回在方法列表中查找到的IMP,如果为YES,就交换IMP,并返回原来方法的IMP;
如果在方法列表中没有找到对应的方法实现,创建一个method_list_t,把方法信息赋值到method_list_t中,最后再和原来的方法列表进行合并,这种方式添加的方法返回IMP为nil。

测试

step1: 新建一个LBHPerson类,声明两个方法只实现其中一个

//.h
@interface LBHPerson : NSObject

//有声明有实现
- (void)instanceMethod1;
//有声明无实现
- (void)instanceMethod2;

@end

//.m
@implementation LBHPerson

- (void)instanceMethod1
{
    NSLog(@"%s",__func__);
}

@end

//调用
SEL sel1 = @selector(instanceMethod1);
Method method1 = class_getInstanceMethod(LBHPerson.class, sel1);
BOOL res1 = class_addMethod(LBHPerson.class, sel1, method_getImplementation(method1), method_getTypeEncoding(method1));
    
    
SEL sel2 = @selector(instanceMethod2);
Method method2 = class_getInstanceMethod(LBHPerson.class, sel2);
BOOL res2 = class_addMethod(LBHPerson.class, sel2, method_getImplementation(method2), method_getTypeEncoding(method2));
    
NSLog(@"== %d  %d",res1,res2);

step2:运行

== 0  1

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

里面的关键函数也是addMethod

测试

还是使用上面的代码

step1: 加上instanceMethod2的实现,在调用的地方加上替换方法并打上断点

- (void)instanceMethod2
{
    NSLog(@"%s",__func__);
}

//调用
class_replaceMethod(LBHPerson.class, sel1, method_getImplementation(method2), method_getTypeEncoding(method2));

step2: 运行

替换成功 method1sel指向method2imp

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

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

交换两个方法的imp

测试
上一篇下一篇

猜你喜欢

热点阅读