Method-Swizzling
1 . class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
会一直找(lookUpImpOrForward),经历快速慢速,一直往上找(父类),找SEL的IMP,有则返回Method,否则返回nil
2.class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
找到当前类是否有SEL, 没有,则添加方法,返回true, 有则不会添加,返回false
-
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
class_addMethod内部屌用的是old = _class_addMethod(cls, name, imp, types, NO);
return !old;
而 class_replaceMethod 内部屌用的是 return _class_addMethod(cls, name, imp, types, YES);if (replace) {
method_setImplementation((Method)m, imp);
}
所以内部原理是class_addMethod逻辑的基础上多一个,查找到的话,会将SEL指向IMP -
void method_exchangeImplementations(Method m1_gen, Method m2_gen)
方法交换,内部会进行参数非空判断,参数有为空的话,不进行交换
只是简单的进行反复交换
void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
IMP m1_imp;
old_method *m1 = oldmethod(m1_gen);
old_method *m2 = oldmethod(m2_gen);
if (!m1 || !m2) return;
impLock.lock();
m1_imp = m1->method_imp;
m1->method_imp = m2->method_imp;
m2->method_imp = m1_imp;
impLock.unlock();
}
问题1:
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person对象方法:%s",__func__);
}
/// LGStudent继承LGPerson
@interface LGStudent : LGPerson
@end
@implementation LGStudent
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
// personInstanceMethod 我需要父类的这个方法的一些东西
// 给你加一个personInstanceMethod 方法
// imp
// 是否递归
- (void)lg_studentInstanceMethod{
[self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
NSLog(@"LGStudent添加的lg对象方法:%s",__func__);
}
@end
/// Method-Swizzling
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
int main(int argc, char * argv[]) {
// 黑魔法坑点一: 子类没有实现 - 父类实现
LGStudent *s = [[LGStudent alloc] init];
[s personInstanceMethod];
// personInstanceMethod -> lg_studentInstanceMethod
LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];
}
问题描述:
父类: LGPerson SEL: personInstanceMethod 方法实现IMP:personInstanceMethod;
子类LGStudent继承SHPerson
此时子类LGStudent +load方法中 交换personInstanceMethod-> lg_studentInstanceMethod
运行结果是是什么?
子类正常
person对象方法:-[LGPerson personInstanceMethod]
LGStudent添加的lg对象方法:-[LGStudent lg_studentInstanceMethod]
父类奔溃
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGPerson lg_studentInstanceMethod]: unrecognized selector sent to instance 0x600002a4c350'
terminating with uncaught exception of type NSException
why
-
[s personInstanceMethod];中不报错是因为 student中的imp交换成了lg_studentInstanceMethod,而LGStudent中有这个方法,所以不会报错
-
崩溃的点在于[p personInstanceMethod];,其本质原因:LGStudent的中进行了方法交换,将person中imp 交换成了 LGStudent中的lg_studentInstanceMethod,然后需要去LGPerson中的找lg_studentInstanceMethod,但是LGPerson中没有lg_studentInstanceMethod方法,即相关的imp找不到,所以就崩溃了
method_exchangeImplementations: 此方法在交换方法时先在当前类查找方法进行交换,没有则找父类
证明:源码
void method_exchangeImplementations(Method m1Signed, Method m2Signed)
{
if (!m1Signed || !m2Signed) return;
method_t *m1 = _method_auth(m1Signed);
method_t *m2 = _method_auth(m2Signed);
mutex_locker_t lock(runtimeLock);
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
m1->setImp(imp2);
m2->setImp(imp1);
// 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, __func__, [sel1, sel2, imp1, imp2](Class c){
return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
});
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
进入flushCaches
static void flushCaches(Class cls, const char *func, bool (^predicate)(Class))
{
runtimeLock.assertLocked();
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
const auto handler = ^(Class c) {
if (predicate(c)) {
c->cache.eraseNolock(func);
}
return true;
};
if (cls) {
foreach_realized_class_and_subclass(cls, handler);
} else {
foreach_realized_class_and_metaclass(handler);
}
}
进入
foreach_realized_class_and_subclass
static inline void
foreach_realized_class_and_subclass_2(Class top, unsigned &count,
bool skip_metaclass,
bool (^code)(Class) __attribute((noescape)))
{
Class cls = top;
runtimeLock.assertLocked();
ASSERT(top);
while (1) {
if (--count == 0) {
_objc_fatal("Memory corruption in class list.");
}
bool skip_subclasses;
if (skip_metaclass && cls->isMetaClass()) {
skip_subclasses = true;
} else {
skip_subclasses = !code(cls);
}
if (!skip_subclasses && cls->data()->firstSubclass) {
cls = cls->data()->firstSubclass;
} else {
while (!cls->data()->nextSiblingClass && cls != top) {
cls = cls->getSuperclass();
if (--count == 0) {
_objc_fatal("Memory corruption in class list.");
}
}
if (cls == top) break;
cls = cls->data()->nextSiblingClass;
}
}
}
查找父类
cls = cls->getSuperclass();
解决方法
使用如下方法进行交换
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
// oriSEL personInstanceMethod
// swizzledSEL lg_studentInstanceMethod
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加你要交换的方法 - lg_studentInstanceMethod, 根据源码得知,尝试查找 当前类是否有oriSEL方法,没有的话,class_addMethod则是成功的,还会把方法真的添加进入
// SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
// 此时来交换就不会影响到父类的方法啦
/**
personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
*/
if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
/// 这个方法会先试着交换自己本类的方法,没有则往父类查找交换,此时就会影响到父类方法啦
method_exchangeImplementations(oriMethod, swiMethod);
}
}
先尝试添加class_addMethod
成功则 class_replaceMethod,失败则method_exchangeImplementations
打印结果:
子类正常
person对象方法:-[LGPerson personInstanceMethod]
LGStudent添加的lg对象方法:-[LGStudent lg_studentInstanceMethod]
父类personInstanceMethod 方法不受影响
person对象方法:-[LGPerson personInstanceMethod]
能解决问题的原理
分析下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;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
ASSERT(types);
ASSERT(cls->isRealized());
method_t *m;
/// 从当前类中查找是否存在SEL: name,本例子中是personInstanceMethod,
/// 在当前类中不存在(存在父类中), 不存在,则走入else中
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp(false);
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
/// 进入这里
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
/// 则进行方法添加,SEL:personInstanceMethod IMP: lg_studentInstanceMethod
/// 之后还会对方法进行排序, 则LGStudent则有个 SEL:personInstanceMethod IMP: lg_studentInstanceMethod
addMethods_finish(cls, newlist);
result = nil;
}
return result;
}
////. 查找当前类要交换的方法
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
class_addMethod 添加LGStudent- SEL:personInstanceMethod IMP: lg_studentInstanceMethod成功后
则进行 class_replaceMethod:(将SEL:lg_studentInstanceMethod又指向 IMP: personInstanceMethod)
此时就是 LGStudent中SEL:personInstanceMethod->IMP: lg_studentInstanceMethod, 子类LGStudent SEL:lg_studentInstanceMethod->父类的IMP: personInstanceMethod
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);
}
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;
/// 此时replace==YES;并且LGStudent之前class_addMethod已经添加personInstanceMethod啦,所有会进入到if中
所以
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp(false);
} else {
//// 然后进入这步
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
addMethods_finish(cls, newlist);
result = nil;
}
return result;
}
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(false);
SEL sel = m->name(); /// 发现此处class_replaceMethod和method_exchangeImplementations在此处的区别了吗。
///method_exchangeImplementations中
/**
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
原先: A1(SEL)->A2(IMP), B1(SEL)->B2(IMP),
A1->B2, B1->A2
*/
/// class_replaceMethod 是
/// IMP old = m->imp(false); SEL sel = m->name() B1->A2
m1->setImp(imp2);
m2->setImp(imp1);
// 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?
///.此时就是将SEL-IMP等同与
m->setImp(imp); LGStudent的SEL: lg_studentInstanceMethod->父类的IMP: personInstanceMethod
flushCaches(cls, __func__, [sel, old](Class c){
return c->cache.shouldFlush(sel, old);
});
adjustCustomFlagsForMethodChange(cls, m);
return old;
}
问题2:
子类没有实现,父类也没有实现,下面的调用有什么问题?
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LGPerson
@end
/// LGStudent继承LGPerson
@interface LGStudent : LGPerson
@end
@implementation LGStudent
@end
int main(int argc, char * argv[]) {
// 黑魔法坑点一: 子类,父类都没有实现
LGStudent *s = [[LGStudent alloc] init];
[s personInstanceMethod];
// personInstanceMethod -> lg_studentInstanceMethod
LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];
}
- (void)lg_studentInstanceMethod{
/// 递归啦
[self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
原因是 栈溢出,递归死循环了,那么为什么会发生递归呢?----主要是因为
/// cls: LGStudent. oriSEL: personInstanceMethod; 没有实现所以oriMethod==nil
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加你要交换的方法 - lg_studentInstanceMethod, 根据源码得知,尝试查找 当前类是否有oriSEL方法,没有的话,class_addMethod则是成功的,还会把方法真的添加进入
// SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
// 此时来交换就不会影响到父类的方法啦
/**
personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
*/
if (success) {// 此时LGStuden添加成功了SEL: personInstanceMethod -> IMP: lg_studentInstanceMethod;
/// 然后执行class_replaceMethod时: oriMethod为nil 所以失败啦,
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
/// 这个方法会先试着交换自己本类的方法,没有则往父类查找交换,此时就会影响到父类方法啦
method_exchangeImplementations(oriMethod, swiMethod);
}
解决方案: Method oriMethod = class_getInstanceMethod(cls, oriSEL);需要进行非空判断下
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}