Swizzle在OC问题排查中的应用

原博文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
 
原文地址:https://www.cnblogs.com/8335IT/p/15501450.html