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 字体根据屏幕尺寸适配
- 为 UIFont 建立一个 Category。
- 在分类中实现一个自定义的 xxx_systemFontOfSize: 方法,在其中添加缩放字体的方法。
- 利用 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 全局处理按钮重复点击
- 为 UIControl 或 UIButton 建立一个 Category。
- 在分类中添加一个 NSTimeInterval xxx_acceptEventInterval; 的属性,设定重复点击间隔
- 在分类中实现一个自定义的 xxx_sendAction:to:forEvent: 方法,在其中添加限定时间相应的方法。
- 利用 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