iOS底层系列19 -- Method-Swizzling

2021-03-12  本文已影响0人  YanZi_33
Method-Swizzling
Snip20210312_114.png
Method-Swizzling常见函数

下面通过几个案例来阐述在使用Method-Swizzling过程中需要注意的事项
准备工作:新建两个类父类YYPerson子类YYStudent,Method-Swizzling帮助类YYRuntimeHelperYYStudent分类(Add)

#import <Foundation/Foundation.h>

@interface YYPerson : NSObject

- (void)walk;

@end

#import "YYPerson.h"

@implementation YYPerson

- (void)walk{
    NSLog(@"%s",__func__);
}

@end
#import <Foundation/Foundation.h>

@interface YYStudent : NSObject

- (void)read;

- (void)write;

@end

#import "YYStudent.h"

@implementation YYStudent

- (void)read{
    NSLog(@"%s",__func__);
}

- (void)write{
    NSLog(@"%s",__func__);
}

@end
#import "YYStudent.h"

@interface YYStudent (Add)

@end

#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"

@implementation YYStudent (Add)

//在运行时,进行方法的交换
+ (void)load{
    [YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(read) swizzledSEL:@selector(yy_read)];
}

- (void)yy_read{
    NSLog(@" 使用yy_read的方法去实现read");
}

@end
#import <Foundation/Foundation.h>

@interface YYRuntimeHelper : NSObject

+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL;

@end

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

@implementation YYRuntimeHelper

+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
    Method originalMethod = class_getInstanceMethod(cls, originalSEL);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSEL);
    //方法交换
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end
案例一:外界主动调用目标类的load方法,导致Method-Swizzling失效
Snip20210312_116.png

上面的截图是正常情况;但如果在AppDelegate.m文件中,主动调用[YYStudent load],那么当代码执行到控制器时,Method-Swizzling就失效了,因为前后交换了两次还原了。

Snip20210312_118.png

解决方案:保证YYStudent(Add)中load方法中的Method-Swizzling只执行一次,加入dispatch_once
YYStudent(Add)修改后的代码如下:

#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"

@implementation YYStudent (Add)

//在运行时,进行方法的交换
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(read) swizzledSEL:@selector(yy_read)];
    });
}

- (void)yy_read{
    NSLog(@" 使用yy_read的方法去实现read");
}

@end
案例二:子类没有实现目标方法,但是父类实现了目标方法,在子类的分类中交换目标方法的实现时,最终交换的是父类的实现。

例如上面的YYPerson类有walk的实现,但在子类YYStudent中没有walk的实现;YYStudent分类中的交换实现如下:

#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"

@implementation YYStudent (Add)

//在运行时,进行方法的交换
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(walk) swizzledSEL:@selector(yy_walk)];
    });
}

- (void)yy_walk{
   //这里不会出现循环调用,因为已经实现了方法的交换
   [self yy_walk];
    NSLog(@" 使用yy_walk的方法去实现walk");
}

@end

外界调用及LLDB调试如下:

Snip20210315_122.png Snip20210315_121.png

出现了崩溃,首先因为子类YYStudent没有walk方法实现,所以Method-Swizzling交换的是父类的walk,见下图所示:

Snip20210315_123.png

解决方案:在进行方法交换时,首先判断当前类是否有目标方法,如果有直接进行交换;如果没有调用class_addMethod函数,给当前类动态添加一个方法(目标方法),但其实现是交换方法的实现;添加成功之后再调用class_replaceMethod将交换方法的实现替换成目标方法的实现;这样才完整的实现了Method-Swizzling

YYRuntimeHelper修改后的代码如下:

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

@implementation YYRuntimeHelper

+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) {
        return;
    }
    
    Method original_method = class_getInstanceMethod(cls, originalSEL);
    Method swizzled_method = class_getInstanceMethod(cls, swizzledSEL);
    
    IMP original_imp = method_getImplementation(original_method);
    IMP swizzled_imp = method_getImplementation(swizzled_method);
    
    //主要用来检测当前类是否有original_method,避免替换了父类的实现
    //originalSEL --> swizzled_imp
    BOOL success = class_addMethod(cls, originalSEL, swizzled_imp, method_getTypeEncoding(original_method));
    //添加成功,表示当前类没有original_method
    if (success) {
        //swizzledSEL --> original_imp
        //因为当前类没有original_method 这里的original_imp是当前类父类的实现
        class_replaceMethod(cls, swizzledSEL, original_imp, method_getTypeEncoding(original_method));
    }else{//添加失败,表示当前类有original_method
        //方法交换
        method_exchangeImplementations(original_method,swizzled_method);
    }
}

@end

LLDB调试结果如下:

Snip20210312_120.png
案例三:子类没有实现目标方法,且父类也没有实现目标方法。

外界调用及LLDB调试结果如下:

Snip20210315_124.png Snip20210315_125.png

Method-Swizzling的代码实现:

Snip20210315_126.png Snip20210315_128.png

解决方案:在进行Method-Swizzling之前,对原始方法进行检测,若为空调用class_addMethod函数,添加原始方法walk,方法实现为交换方法实现yy_walk;然后将交换方法yy_walk的实现置为{},即为空方法什么也不做,接着再调用class_addMethod函数,此时上面已经添加过了,所以再次添加会不成功,最后走method_exchangeImplementations函数,但由于original_method是空的,所以不会发生交换,最终的效果如下所示:

Snip20210315_130.png

YYRuntimeHelper修过之后的代码如下:

+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) {
        return;
    }
    
    Method original_method = class_getInstanceMethod(cls,originalSEL);
    Method swizzled_method = class_getInstanceMethod(cls,swizzledSEL);
    
    IMP original_imp = method_getImplementation(original_method);
    IMP swizzled_imp = method_getImplementation(swizzled_method);
    
    if (!original_method) {
        //在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现
        class_addMethod(cls,originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
        
        method_setImplementation(swizzled_method, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }

    BOOL didAddMethod = class_addMethod(cls,originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
    if (didAddMethod) {
        class_replaceMethod(cls,swizzledSEL,original_imp, method_getTypeEncoding(original_method));
    }else{
        method_exchangeImplementations(original_method,swizzled_method);
    }
}

method_exchangeImplementations源码实现如下:

Snip20210315_131.png
Method-Swizzling之类方法

上面介绍的是实例方法的交换,类方法的交换原理是类似的,只不过类方法存储在元类,具体实现代码如下:

+ (void)yy_classMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
    if (!cls) {
        return;
    }
    
    class_getClassMethod(cls,originalSEL);
    Method original_method = class_getClassMethod([cls class],originalSEL);
    Method swizzled_method = class_getClassMethod([cls class],swizzledSEL);
    
    IMP original_imp = method_getImplementation(original_method);
    IMP swizzled_imp = method_getImplementation(swizzled_method);
    
    if (!original_method) {
        //在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现
        //object_getClass(cls) 获取元类
        class_addMethod(object_getClass(cls),originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
        method_setImplementation(swizzled_method,
                                 imp_implementationWithBlock(^(id self,SEL _cmd){
            NSLog(@"来了一个空的 imp");
        }));
    }

    BOOL didAddMethod = class_addMethod(object_getClass(cls),originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
    if (didAddMethod) {
        class_replaceMethod(object_getClass(cls),swizzledSEL,original_imp, method_getTypeEncoding(original_method));
    }else{
        method_exchangeImplementations(original_method,swizzled_method);
    }
}
上一篇下一篇

猜你喜欢

热点阅读