iOS - Runtime Method Swizzling(中)

简介: Runtime合集 iOS - Runtime基础

4. Method Swizzling 应用场景


Method Swizzling 可以交换两个方法的实现,在开发中更多的是应用于系统类库,以及第三方框架的方法替换。在官方不公开源码的情况下,我们可以借助 Runtime 的 Method Swizzling 为原有方法添加额外的功能


4.1 全局页面统计功能

@implementation UIViewController (HIAnalysis)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(analysis_viewDidLoad);
        [HIHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
        originalSelector = @selector(viewWillAppear:);
        swizzledSelector = @selector(analysis_viewWillAppear:);
        [HIHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
        originalSelector = @selector(viewDidAppear:);
        swizzledSelector = @selector(analysis_viewDidAppear:);
        [HIHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
        originalSelector = @selector(viewWillDisappear:);
        swizzledSelector = @selector(analysis_viewWillDisappear:);
        [HIHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
        originalSelector = @selector(viewDidDisappear:);
        swizzledSelector = @selector(analysis_viewDidDisappear:);
        [HIHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
    });
}
- (void)analysis_viewDidLoad{
    [self analysis_viewDidLoad];
}
- (void)analysis_viewWillAppear:(BOOL)animated{
    void(^fetchTitleHandler)(void) = ^(){
        NSString *title = self.title;
        if (!isStrEmpty(title) &&
            ![self isKindOfClass:[UINavigationController class]] &&
            ![self isKindOfClass:[UITabBarController class]]) {
            [HIAppAnalytics HI_trackPageBegin:title];
        }
    };
    if ([self respondsToSelector:@selector(analysisName)]) {
        NSString *analysisName = [self performSelector:@selector(analysisName)];
        if (!isStrEmpty(analysisName)) {
            [HIAppAnalytics HI_trackPageBegin:analysisName];
        }else{
            fetchTitleHandler();
        }
    }else{
        fetchTitleHandler();
    }
    [self analysis_viewWillAppear:animated];
}


4. 2 字体根据屏幕尺寸适配


  1. 为 UIFont 建立一个 Category。


  1. 在分类中实现一个自定义的 xxx_systemFontOfSize: 方法,在其中添加缩放字体的方法。


  1. 利用 Method Swizzling 将 systemFontOfSize: 方法和 xxx_systemFontOfSize: 进行方法交换。

#import "UIFont+AdjustSwizzling.h"
#import <objc/runtime.h>
#define XXX_UISCREEN_WIDTH  375
@implementation UIFont (AdjustSwizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类方法存储在元类对象中,我们通过 object_getClass 获取 self.isa 所指向的元类
        Class class = object_getClass((id)self);
        SEL originalSelector = @selector(systemFontOfSize:);
        SEL swizzledSelector = @selector(xxx_systemFontOfSize:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
+ (UIFont *)xxx_systemFontOfSize:(CGFloat)fontSize {
    UIFont *newFont = nil;
    newFont = [UIFont xxx_systemFontOfSize:fontSize * [UIScreen mainScreen].bounds.size.width / XXX_UISCREEN_WIDTH];
    return newFont;
}
@end


4.3 全局处理按钮重复点击


  1. 为 UIControl 或 UIButton 建立一个 Category。


  1. 在分类中添加一个 NSTimeInterval xxx_acceptEventInterval; 的属性,设定重复点击间隔


  1. 在分类中实现一个自定义的 xxx_sendAction:to:forEvent: 方法,在其中添加限定时间相应的方法。


  1. 利用 Method Swizzling 将 sendAction:to:forEvent: 方法和 xxx_sendAction:to:forEvent: 进行方法交换。

#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>
@interface UIButton()
// 重复点击间隔
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
// 上一次点击时间戳
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventTime;
@end
@implementation UIButton (DelaySwizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(sendAction:to:forEvent:);
        SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
- (void)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    // 如果想要设置统一的间隔时间,可以在此处加上以下几句
    if (self.xxx_acceptEventInterval <= 0) {
        // 如果没有自定义时间间隔,则默认为 0.4 秒
        self.xxx_acceptEventInterval = 0.4;
    }
    // 是否小于设定的时间间隔
    BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
    // 更新上一次点击时间戳
    if (self.xxx_acceptEventInterval > 0) {
        self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
    }
    // 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件
    if (needSendAction) {
        [self xxx_sendAction:action to:target forEvent:event];
    }
}
- (NSTimeInterval )xxx_acceptEventInterval{
    return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue];
}
- (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
    objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval )xxx_acceptEventTime{
    return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue];
}
- (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
    objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end


相关文章
|
API iOS开发
iOS面试关于runtime
iOS面试关于runtime
120 0
15-iOS之Runtime常用API以及使用
15-iOS之Runtime常用API以及使用
108 0
|
编译器 iOS开发
iOS Runtime详细介绍及实战使用(二)
iOS Runtime详细介绍及实战使用
 iOS Runtime详细介绍及实战使用(二)
|
API iOS开发
iOS Runtime详细介绍及实战使用(一)
iOS Runtime详细介绍及实战使用
|
iOS开发
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
144 0
|
JSON 前端开发 JavaScript
iOS Principle:Runtime(下)
iOS Principle:Runtime(下)
97 0
iOS Principle:Runtime(下)
|
缓存 编译器 Swift
iOS Principle:Runtime(中)
iOS Principle:Runtime(中)
173 0
iOS Principle:Runtime(中)
|
存储 缓存 编译器
iOS Principle:Runtime(上)
iOS Principle:Runtime(上)
114 0
iOS Principle:Runtime(上)
|
iOS开发
iOS - Runtime 动态添加属性
我们在开发中常常使用类目Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法,而且相比类目更是具有增加属性的优势,但是继承毕竟是一个重量级的操作,添加不必要的继承关系无疑增加了代码的复杂度。