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;
}
将方法m
的imp
指向传进来的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:
运行
替换成功 method1
的sel
指向method2
的imp
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