iOS Method Swizzle 代码
+ (void)swizzleOriginalSEL:(SEL)originalSEL withSwizzlingSEL:(SEL)swizzlingSEL {
1.Method originalMethod = class_getInstanceMethod(self, originalSEL);
2.Method swizzlingMethod = class_getInstanceMethod(self, swizzlingSEL);
3.Boolean isAddMethod = class_addMethod(self, originalSEL,
method_getImplementation(swizzlingMethod),
method_getTypeEncoding(swizzlingMethod));
if (isAddMethod) {
4.class_replaceMethod(self, swizzlingSEL,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
5. method_exchangeImplementations(originalMethod, swizzlingMethod);
}
}
平常我们用的方法都是method_exchangeImplementations
,其实这种用法是错误的,我们正确的替换方法应该上面的代码。我直接说明上面的代码与直接调用method_exchangeImplementations
的区别:为了防止我们所替换子类的实例方法是继承与父类,而且我们并没有在子类中重写该方法。如果直接调用method_exchangeImplementations
方法那么我们会直接替换了父类中的方法实现,导致继承此父类的所有子类对应的改实例方法的实现被转换,如果大家已经明白了这个道理就可以绕行了(😆)。我也会介绍一下这几个方法的源码,以及一些runtime基本的知识。
class_replaceMethod 和 class_addMethod 源码分析
class_addMethod
源码
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
// 如果cls为nil,直接返回NO,添加方法失败
if (!cls) return NO;
// 加锁,我们只需要明白这是加锁为了防止多线程访避免数据同步问题
rwlock_writer_t lock(runtimeLock);
// 实现函数,看最后一个函数我们传递的是NO,这个参数要注意。因为我们下面可以看到,我们分析class_replaceMehod源码中也会看到我们调用其实也是addMehtod方法不过最后一个参数是YES
return ! addMethod(cls, name, imp, types ?: "", NO);
}
class_replaceMethod
源码
// 代码注释请参考class_addMethod
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
rwlock_writer_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
下面分析addMethod
源码
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
// 加锁
runtimeLock.assertWriting();
// 一些断言函数,第一个是保证types必须有值,第二个保证cls是已经实现的状态
assert(types);
assert(cls->isRealized());
method_t *m;
// getMethodNoSuper_nolock 这个方法非常的关键,他的含义是只在本类中查找是否含有方法,而不再父类中查找。
if ((m = getMethodNoSuper_nolock(cls, name))) {
// 可以从本类中查找方法
if (!replace) {
// replace 我们在class_addMethod方法中传递的是NO,所以我们这个方法会走这一步,或者其他。如果我们查找的方法结构体中函数指针是有值,那么返回YES,但是class_addMethod会对result取反,所以我们知道如果我们将要添加的method,在cls中存在,并且已经有了函数指针那么我们添加方法失败。
result = m->imp;
} else {
// repalce 我们在class_replaceMethod中传递的是YES,class_replaceMethod可能会走这一步。
result = _method_setImplementation(cls, m, imp);
}
} else {
// 没有在本类中查找到对应的方法,则我们在class中添加method,而method的结构体系:class->method_array_t(methods)->method_list_t->method
// method 的体系会在后面讲解
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);
flushCaches(cls);
// 返回result是NO,那么class_addMethod取反,那么返回YES
result = nil;
}
return result;
}
现在我们分析_getMethodNoSuper_nolock()
方法,其实这个方法是我们在class中查找method的根本方法,我们在class_getInstanceMethod
中也是调用了这方法。
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
// for循环cls(本类)属性mehtods,methods中包含每一个method_list_t,之后再method_list_t中根据sel来查找method
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
分析search_method_list
方法
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
// 在已经进行过排序的method列表中查找
return findMethodInSortedMethodList(sel, mlist);
} else {
// 这个是我们关注的。我们根据sel作为key,来查找method
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
根据上面的代码我们可以大致了解到,如果没有查找到对应的method,我们会给class中的methods
属性中添加一个method_list_t
,而method_list_t
中储存着method
。那么我们可以得知method
的储存结构是数组中包含着数组,子数组中包含着Method,这样设计的目的是区分开Method来自哪里,是来自自己本身、父类、分类、扩展、runtime添加。当我们大致了解了method
结构我们在来看看一些addMethod
方法中else这一部分的代码
// 创建一个method_list_t *newlist结构体,并且分配内存,和循环标识
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
//对newlist进行复制操作,
newlist->count = 1;
//first,这个属性的类型是Method,所以这里相当于给Method赋值,name就是SEL
newlist->first.name = name;
// 方法type进行赋值
newlist->first.types = strdupIfMutable(types);
// 函数指针进行赋值
newlist->first.imp = imp;
// 对method_list_t进行准备操作
prepareMethodLists(cls, &newlist, 1, NO, NO);
// 将生成好的method_list_t放在methods(method_array_t)中,也就是重新生成一个method_list_t放在class中
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
// 返回result是NO,那么class_addMethod取反,那么返回YES
result = nil;
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.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
//这一部分是我们关注的
return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
rwlock_reader_t lock(runtimeLock);
// 关注这一部分
return getMethod_nolock(cls, sel);
}
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
// fixme nil cls?
// fixme nil sel?
assert(cls->isRealized());
// 其实在查找method中,我们依然依靠了getMethodNoSuper_nolock方法,只不过是在一个我们会根据superclass来进行循环
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
代码步骤分析
- 1 & 2获取method结构体,
class_getInstanceMethod
获取的method可能是在本身的class
中获取,或者是在superclass
中获取 - 3 首先查找
original mehod
是否可以在本类中查找到,如果查到不到,则在本类中添加一个method
,而且method
的实现方法是swizzle method
的实现方法 - 4 如果添加方法成功,首先说明了本类中没有
original method
,我们在第一步中获取的original method
是在父类中的,所以我们自己在本类中创建了一个new original method
, 现在new original method
与第一步获取的original method
没有任何的关系。那么现在new original Method
的IMP
是swizzle IMP
,那么我现在调用class_replaceMethod
可以把可能从父类中拿到的original method
中的实现方法赋值给swizzle method
,这样就完成了方法替换 - 5 如果方法添加没有成功,说明本类中含有这个method,我们直接调用
method_exchangeImplementations
进行方法IMP替换
Load 方法中调用 method swizzle
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleOriginalSEL:@selector(sendEvent:) withSwizzlingSEL:@selector(swizzle_sendEvent:)];
});
}
为什么要在load方法中增加dispathc_once
,因为load方法中本来就会调用一次,我在一篇简书评论中看见一位人这样评论,防止人为调用load
,即使这种场景非常少,我感觉这这个解答相当给力,作为程序猿我们应该有意识来方法来进行人为防范,不要想着load
真的只会调用一次。如果对load和initialize 不是很了解,可以看看我的另外一篇文(就当增加一些人气了,不然的话太尴尬了。)
网友评论