原博文https://newrelic.com/blog/best-practices/right-way-to-swizzle
1 、Swizzling是通过使用另一个方法替换该方法的实现来更改方法功能的行为,通常是在运行时。使用swizzling有许多不同的原因:内省、重写默认行为,甚至可能是动态方法加载。我看到很多博客文章讨论Objective-C中的swizzling,其中很多都推荐了一些非常糟糕的做法。如果您正在编写独立的应用程序,这些糟糕的做法其实并不是什么大问题,但是如果您正在为第三方开发人员编写框架,swizzling可能会打乱一些保持一切顺利运行的基本假设。那么,在Objective-C中使用swizzle的正确方法是什么?
让我们从基础开始。当我说swizzling时,我指的是用我自己的方法替换原始方法的行为,通常是从替换方法中调用原始方法。Objective-C允许使用Objective-C-runtime提供的函数执行此操作。在运行时,Objective-C方法表示为一个名为Method的C结构;
2、知识点
SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。其中@selector()是取类方法的编号,取出的结果是SEL类型,SEL methodId = @selector(func);
IMP:一个函数指针,保存了方法的地址
-(void)addNewMethod:(NSString *)str{} 取得SEL的方法 SEL sel = @selector(addNewMethod:); 取得方法名(编号) NSString *methodName1 = NSStringFromSelector(@selector(addNewMethod:)); 取得IMP指针 IMP imp = [self methodForSelector:@selector(addNewMethod:)] 或者 IMP imp = [NSObject instanceMethodForSelector:@selector(addNewMethod:)]
3 、相关的API:
利用
利用
利用
方法交换类的实现:利用
method_exchangeImplementations
来交换2个方法中的IMP。利用
class_replaceMethod
来修改类。利用
method_setImplementation
来直接设置某个方法的IMP。/** * Sets the implementation of a method. * * @param m The method for which to set an implementation. * @param imp The implemention to set to this method. * * @return The previous implementation of the method. */ OBJC_EXPORT IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Exchanges the implementations of two methods. * * @param m1 Method to exchange with second method. * @param m2 Method to exchange with first method. * * @note This is an atomic version of the following: * code * IMP imp1 = method_getImplementation(m1); * IMP imp2 = method_getImplementation(m2); * method_setImplementation(m1, imp2); * method_setImplementation(m2, imp1); * endcode */ OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
method_exchangeImplementations就相当于调用了两次method_setImplementation
4、下边是替换测试demo:
#import "ViewController.h" #import <objc/message.h> @interface ViewController () - (void) swizzleExample; - (int) originalMethod; @end static IMP __original_Method_Imp; int _replacement_Method(id self, SEL _cmd)//参数没有也行,所有的OC方法传递了两个隐藏的参数:对self的引用(id self),和方法名selector(SEL _cmd) {
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);cmd是
originalMethod,这里没有跳断言。所以我们改变的只是IMP
printf("2222222222");
return 2;
}
@implementation ViewController
- (void) swizzleExample //call me to swizzle
{
Method m = class_getInstanceMethod([self class],
@selector(originalMethod));
__original_Method_Imp = method_setImplementation(m,
(IMP)_replacement_Method);//单边替换
}
- (int) originalMethod
{
printf("11111111111");
return 1;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
int originalReturn = [self originalMethod];
[self swizzleExample];
int swizzledReturn = [self originalMethod];//最终调用的是22222
assert(originalReturn == 1); //true
assert(swizzledReturn == 2); //true
}
@end