黑魔法 Method_Swizzling
- 原理: Method_Swizzling是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzle代码写到任何地方,但是只有在Method_Swizzling这段Method Swizzle代码执行完毕之后互换才起作用。
- Method_Swizzling交换时机:尽可能在+load方法中实现
- +load的执行时机:+load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,main函数之前,就会调用每个类的 +load 方法
- 子类的+load方法会在它的所有父类(不包括父类分类中的+load)的+load方法执行之后执行
- 分类的+load方法会在所有的主类的+load方法执行之后执行
- 不同的类之间的+load方法的调用顺序是不确定的
- + initialize:方法类似一个懒加载,initialize是在类或者其子类的第一个方法被调用前调用,且默认只加载一次;+ initialize 的调用发生在 +init 方法之前。
- +load 和 + initialize 差异比较
|
+(void)load |
+(void)initialize |
执行时机 |
在main函数之前执行 |
在类的方法被第一次调用时执行 |
若自身未定义,是否沿用父类的方法? |
否 |
是 |
类别中的定义 |
全都执行,但后于类中的方法 |
覆盖类中的方法,只执行一次 |
- 原子性:使用dispatch_once来执行方法交换,这样可以保证只运行一次
- Method_Swizzling的两种实现
- 直接交换: (在子类中交换方法,原始方法子类未实现,父类实现时,会将父类中的方法也交换)
BOOL simple_Swizzle(Class aClass,SEL originalSel, SEL swizzleSel)
{
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass,swizzleSel);
method_exchangeImplementations(originalMethod, swizzleMethod);
return YES;
}
- 示例: A类有方法work,B类继承自A类,有方法b_work,此时在B类分类的+load方法中交换work和b_work,并在b_work中调自身(回调回work),此时B对象b,调用work,实际调用的是b_work后再调用work(由于B继承自A,所以可以找得到work方法),A对象a,调用work,实际调用也是b_work,由于A类未实现b_work方法,出现崩溃
- 先添加再交换:(保证只在子类中交换方法,不影响父类)
- 这种方法会先尝试给自己添加work方法实现,如果B类没有实现work方法,class_addMethod会成功添加一个新的属于son自己的work方法,同时将本来要swizzle的方法的实现直接复制进work里,然后再将父类的IMP给swizzle
BOOL best_Swizzle(Class aClass, SEL originalSel, SEL swizzleSel)
{
// 如果originalSel没有实现过,class_getInstanceMethod无法找到该方法,所以originalMethod为nil
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
// 当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzleMethod);
}
return YES;
}
- swizzle一个子类到父类到根类都没有实现过的方法,会出现死循环,死循环是由于didAddMethod成功了,所以调用originalSel实际调用的是swizzleSel,class_replaceMethod交换失败了,所以在swizzleSel中调用originalSel,其实调用的还是自身,所以出现死循环
- 解决方法是在originalMethod为nil时,替换后将swizzleSel复制一个不做任何事的空实现
if (!originalMethod) {
class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
method_setImplementation(swizzleMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
Method_Swizzling的具体用途AOP(面向切面编程)
- 页面统计(AOP)、NSMutableArray的insert等插入nil,hook: 給全局图片名称添加前缀,分类中为已有的属性或者方法添加钩子(增加一段代码)
- 用于记录或者存储,比方说记录ViewController进入次数、Btn的点击事件、ViewController的停留时间等等。 可以通过Runtime获取到具体ViewController、Btn信息,然后传给服务器
- 添加需要而系统没提供的方法,比方说修改Statusbar颜色。用于轻量化、模块化处理
- 用Method Swizzle 动态给指定的方法添加代码,以解决Cross-cutting concern的编程方式叫做Aspect Oriented Programming,将逻辑处理和事件记录的代码解耦
- AOP可以把琐碎的事务从主逻辑中分离出来,作为单独的模块,它是对面向对象编程模式的一种补充
- 比较好的AOP库,封装了runtime,Method Swizzling这些黑科技,该库只有两个API
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
Objective-C类的继承、方法的重写和重载
- 继承:Objective-c中类的继承与C++类似,不同的是Objective-c不支持多重继承,一个类只能有一个父类,单继承使Objective-c的继承关系很简单,易于管理程序
- 重写:在Objective-c中,子类可继承父类中的方法,而不需要重新编写相同的方法,直接可以使用父类的方法。
但有时我们不想使用使用父类方法,而是想作一定的修改,怎么办呢?只要将子类中书写一个与父类具有相同的方法名、返回类型和参数,就可以将将父类的方法覆盖重写
OverRide *overRide = (OverRide*)[[NSClassFromString(@"SubOverRide") alloc] init];
[overRide superMethod];
- 继承中方法调用的流程:首先到子类中去找,如果有该方法,就调用该方法,如果没有,就到父类中去找,父类没有就去父类的父类找,最后都没有找到,程序崩溃
- 重载:在Objective-c中,方法是不能重载的。也就是说我们不能在类中定义这样的两个方法:它们的名子相同,参数个数相同,参数类型不同,不同的返回值类型。否则Xcode会报错。
引用
网友评论
{
// 如果originalSel没有实现过,class_getInstanceMethod无法找到该方法,所以originalMethod为nil
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
// 当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzleMethod);
}
return YES;
}
// 当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现
这行注释简直是胡写!如果originalMethod为nil,添加方法的操作就会成功,判断条件didAddMethod正好为真,方法的exchange不会进行,恰好是repalce会执行,别扯求蛋了,行吗。扰乱视听。