编写高质量代码的52个有效方法

52个有效方法(13) - 用“方法调配技术”调试“黑盒方法”

2018-09-03  本文已影响8人  SkyMing一C

方法调配技术也就是方法交换技术。

//获取类的实例方法 返回一个Method对象
class_getInstanceMethod(Class obj, SEL cmd)
//获取类的类方法 返回一个Method对象
class_getClassMethod(Class obj, SEL cmd)
//添加一个新的方法和该方法的具体实现
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,  const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//Class cls 你要添加新方法的那个类
//SEL name 要添加的方法
//IMP imp 指向实现方法的指针   就是要添加的方法的实现部分
//const char *types 我们要添加的方法的返回值和参数 method_getTypeEncoding(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//替换方法的实现
method_exchangeImplementations(method1, method2)
交换对象方法
//.h
#import <UIKit/UIKit.h>

@interface UIActionSheet(Swizzling)

@end
#import "UIActionSheet+Swizzling.h"

@implementation UIActionSheet(Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取class
        Class class = [self class];
        // 获取原有SEL
        SEL originSelector = @selector(dismissWithClickedButtonIndex:animated:);
        // 获取交换SEL
        SEL newSelector = @selector(swizzleDismissWithClickedButtonIndex:animated:);
        // 获取原有方法
       Method oriMethod = class_getInstanceMethod(class, originSelector);
        // 获取交换方法
        Method newMethod = class_getInstanceMethod(class, newSelector);
        // 注意:不能直接交换方法实现,需要判断原有方法是否存在,存在才能交换
        // 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在
        // 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现
        // 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现
        BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
        if (isAddedMethod) {
        // 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
            class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        } else {
            method_exchangeImplementations(oriMethod, newMethod);
        }
    });
}

-(void)swizzleDismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated{
    //这个方法是准备和`dismissWithClickedButtonIndex:animated:`这儿方法互换的。在运行期,`swizzleDismissWithClickedButtonIndex:animated: `选择子实际上对应于原有的`dismissWithClickedButtonIndex:animated:`方法的实现
    [self swizzleDismissWithClickedButtonIndex:buttonIndex animated:animated];
    //修复keyWindow 不正确的问题
    [[UIApplication sharedApplication].delegate.window makeKeyWindow];
}
@end
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       //do something...
    });
}
        //获取class
        Class class = [self class];
        // 获取原有SEL
        SEL originSelector = @selector(dismissWithClickedButtonIndex:animated:);
        // 获取交换SEL
        SEL newSelector = @selector(swizzleDismissWithClickedButtonIndex:animated:);
        // 获取原有方法
       Method oriMethod = class_getInstanceMethod(class, originSelector);
        // 获取交换方法
        Method newMethod = class_getInstanceMethod(class, newSelector);
        BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
        if (isAddedMethod) {
            // 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
            class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        } else {
            method_exchangeImplementations(oriMethod, newMethod);
        }
-(void)swizzleDismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated{
    //这个方法是准备和`dismissWithClickedButtonIndex:animated:`这儿方法互换的。在运行期,`swizzleDismissWithClickedButtonIndex:animated: `选择子实际上对应于原有的`dismissWithClickedButtonIndex:animated:`方法的实现
    [self swizzleDismissWithClickedButtonIndex:buttonIndex animated:animated];
    //修复keyWindow 不正确的问题
    [[UIApplication sharedApplication].delegate.window makeKeyWindow];
}
交换类方法
//.h
#import <Foundation/Foundation.h>

@interface UIFont (DefaultSize)

@end
//.m
#import "UIFont+DefaultSize.h"
#import <objc/runtime.h>
@implementation UIFont (DefaultSize)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取class
        Class class = object_getClass((id)self);
        // 获取原有SEL
        SEL originSelector = @selector(preferredFontForTextStyle:);
        // 获取交换SEL
        SEL newSelector = @selector(swizzlePreferredFontForTextStyle:);
        // 获取原有方法
        Method oriMethod = class_getClassMethod(class, originSelector);
        // 获取交换方法
        Method newMethod = class_getClassMethod(class, newSelector);
        // 注意:不能直接交换方法实现,需要判断原有方法是否存在,存在才能交换
        // 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在
        // 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现
        // 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现
        BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
        if (isAddedMethod) {
            // 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
            class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        } else {
            method_exchangeImplementations(oriMethod, newMethod);
        }
    });
}

+ (UIFont *)swizzlePreferredFontForTextStyle:(UIFontTextStyle)style{
    return [UIFont systemFontOfSize:17];
}

@end
要点
  1. 在运行期,可以向类中新增或替换选择子所对应的方法实现。

  2. 使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。

  3. 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。

上一篇下一篇

猜你喜欢

热点阅读