iOS开发 - Method Swizzling的实现

iOS Method Swizzling的实现

因为Objective-C的runtime机制, Method Swizzling这个黑魔法解决了我们实际开发中诸多常规手段所无法解决的问题, 比如代码的插桩,Hook,Patch等等。最近查看AFNetworking源码中就使用了Method Swizzling。

Method Swizzling常用的方法


// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的List
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
// 备注:当判断一个实例方法是否实现时,第一个参数要用类对象,也就是[Person class]。
    当判断一个类方法是否实现时,第一个参数要传元类,也就是object_getClass([Person class])。


场景一

我们经常看到在UIViewController中会在不改变原代码的情况下,添加一些新的事件,比如记录每次进入的ViewController,这时候我们可以创建一个UIViewControler的Category


#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

	// 在ViewController中添加方法,class_addMethod这个方法的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。
        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
	    //如果要修改已存在实现,可以使用class_replaceMethod或者更深一步使用method_setImplementation,
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
	    // 我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
	    // 进行方法实现的交换
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    // 这里不会产生递归调用的
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
@end

系统调用UIViewController的viewWillAppear方法时,实际上执行的是我们实现的xxx_viewWillAppear方法。而我们在xxx_viewWillAppear方法内部调用[self swizzlingViewDidLoad];时,执行的是UIViewController的viewWillAppear方法。

场景二 不同类的方法替换

例如已知一个className为Car的类中有一个实例方法,已知有一个MyCar类,这两个类没有继承等关系。

Car

Car.h

import <Foundation/Foundation.h>

@interface Car : NSObject

- (void)run;

@end

Car.m

@implementation Car

- (void)run {
    NSLog(@"Car RUN");
}

@end


MyCar

MyCar.h

@interface MyCar : NSObject

- (void)dy_run;

@end

Car.m

#import "MyCar.h"
#import <objc/runtime.h>

@implementation MyCar

+ (void)load {
        Class originalClass = NSClassFromString(@"Car");
    
    Class swizzledClass = [self class];
    
    SEL originalSelector = NSSelectorFromString(@"run");
    
    SEL swizzledSelector = @selector(dy_run);
    
    
    // car
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    
    // mycar
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
    
    // 替换给定的originalClass中的方法 originalSelector 的实现
    class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
}

- (void)dy_run {
    NSLog(@"dy_run");
}

  • 使用 class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

具体调用的使用


    Car *car = [[Car alloc] init];
    [car run];

//    [car performSelector:NSSelectorFromString(@"dy_run")];

    MyCar *myCar = [[MyCar alloc] init];
    [myCar dy_run];


打印结果

2019-03-19 09:46:33.250563 AppDemo[4487:874286] dy_run
2019-03-19 09:46:33.250640 AppDemo[4487:874286] dy_run

由此看出,Car调用run方法,打印了dy_run。那class_replaceMethod就是替换给定的originalClass中的方法 originalSelector 的实现。

  • 那如果将class_replaceMethod换成method_exchangeImplementations
	
        class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

	将这行代码改成以下的
	
	// 方法实现交换
	method_exchangeImplementations(originalMethod, swizzledMethod);


当使用这个method_exchangeImplementations方法的时候

打印结果

2019-03-19 10:47:47.132487 AppDemo[4627:899522] dy_run
2019-03-19 10:47:47.132533 AppDemo[4627:899522] Car RUN

可以看到Car调用run方法,打印了dy_run;相应的MyCar调用dy_run,打印了Car RUN。
method_exchangeImplementations将两个方法实现交换

class_addMethod

  • 当使用这个class_addMethod方法的时候,

Class originalClass = NSClassFromString(@“Car”);

BOOL registerMethod = class_addMethod(originalClass, swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

这时候将Car类中新添加了一个方法dy_run。这时候可以使用 [car performSelector:NSSelectorFromString(@“dy_run”)];进行调用

打印结果

2019-03-19 10:53:12.182487 AppDemo[4627:899522] dy_run

这时候Car类中就有了一个dy_run方法了,Car类对象就可以调用了。

猜你喜欢

转载自blog.csdn.net/gloryFlow/article/details/131677505