博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ios开发-面向切面编程(runtime的一个基本应用)
阅读量:4146 次
发布时间:2019-05-25

本文共 9865 字,大约阅读时间需要 32 分钟。

一、简介

一、所谓的aop编程(面向切面编程),其原理也就是在不更改正常的业务流程的前提下,通过生成一个动态代理类,从而实现对目标对象嵌入附加的操作。在ios中,想要实现相似的效果也很简单,利用OC的动态性,通过Method Swizzling 改变目标函数的selector所指向的实现,然后在新的实现中实现附加的操作,完成之后再货到原来的处理逻辑。

二、在一个类没有实现源码的情况下,如果你要改变一个类的实现方法,你可以选择重继承该类,然后重写方法,或者使用Category类别名暴力抢先的方式。但是这两种方式,都需要我们在使用的时候改变我们的编程方式,或者继承该类,或者需要引入Category。下面推出的一种方式,不需要我们修改我们编写逻辑的代码,就能实现函数的Hook功能,那就是RunTime中的Method Swizzling—交换方法的实现。

二、实现原理

在Object-C中每一个Method都是由一个SEL(方法名的散列值)和一个方法实现的指针(IMP)组成,他们在类实例化过程中SEL和IMP一一对应组成我们需要的完整的Method。

struct objc_method {    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;    char * _Nullable method_types                            OBJC2_UNAVAILABLE;    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;}                                                            OBJC2_UNAVAILABLE;

如果我们不做任何处理,SEL和IMP都是一一对应的。如果我们使用Method Swizzling交换Method1和Method2的实现的时候,我们只需要在运行时把IMP2和IMP3的指向地址做个交换就可以了。其实我们调用的就是RunTime中的

/**  * Exchanges the implementations of two methods. *  * @param m1 Method to exchange with second method. * @param m2 Method to exchange with first method. *  * @note This is an atomic version of the following: *  \code  *  IMP imp1 = method_getImplementation(m1); *  IMP imp2 = method_getImplementation(m2); *  method_setImplementation(m1, imp2); *  method_setImplementation(m2, imp1); *  \endcode */OBJC_EXPORT voidmethod_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)     OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

第一个参数为:原始方法名。

第二个参数为:交换方法名。

四、页面埋点的实现

我们新建一个UIViewController的分类

#import 
#import
NS_ASSUME_NONNULL_BEGIN@interface UIViewController (AOP)@end@interface UIControl (AOP)@endtypedef void(^StopScrollBlock)(UIScrollView *scrollView);@interface UIScrollView (AOP)@property(nonatomic, copy) StopScrollBlock stopScrollBlock;@endNS_ASSUME_NONNULL_END
#import "UIViewController+AOP.h"@implementation UIViewController (AOP)+ (void)load{    Method originalMtd = class_getInstanceMethod([self class], @selector(viewDidAppear:));    Method swizzleMtd = class_getInstanceMethod([self class], @selector(aop_viewDidAppear:));    method_exchangeImplementations(originalMtd, swizzleMtd);        Method originalMtd1 = class_getInstanceMethod([self class], @selector(viewWillDisappear:));    Method swizzleMtd1 = class_getInstanceMethod([self class], @selector(aop_viewWillDisAppear:));    method_exchangeImplementations(originalMtd1, swizzleMtd1);}- (void)aop_viewDidAppear:(BOOL)animated{    /**     MTA业务操作 记录日志,上传服务器,页面显示     */    [self aop_viewDidAppear:animated];}- (void)aop_viewWillDisAppear:(BOOL)animated{    /**     MTA业务操作 记录日志,上传服务器,页面消失     */#ifdef DEBUG    if ([self isContainScrollView]) {        /**         关闭滑动场景的卡顿监控,在相关页面进行如下代码的打点         */    }#endif    [self aop_viewWillDisAppear:animated];}-(BOOL)isContainScrollView{    for (id view in self.view.subviews) {        if ([view isMemberOfClass:[UIScrollView class]]) {//判断当前VC是否有UIScrollView及其子类            return YES;        }    }    return NO;}@end@implementation UIControl (AOP)+ (void)load {    Method originalMtd = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));    Method swizzleMtd = class_getInstanceMethod([self class], @selector(aop_sendAction:to:forEvent:));    method_exchangeImplementations(originalMtd, swizzleMtd);        Method originalMtd2 = class_getInstanceMethod([self class], @selector(sendActionsForControlEvents:));    Method swizzleMtd2 = class_getInstanceMethod([self class], @selector(aop_sendActionsForControlEvents:));    method_exchangeImplementations(originalMtd2, swizzleMtd2);}- (void)aop_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {    if ([self isKindOfClass:[UIButton class]]){        /**         MTA次数统计,某按钮点击及频率次数日志         */    }    [self aop_sendAction:action to:target forEvent:event];}// 时长统计- (void)aop_sendActionsForControlEvents:(UIControlEvents)controlEvents {    if (controlEvents == UIControlEventTouchDown){        /**         MTA 处理按钮点击事件         */        NSLog(@"aop_sendAction ============= 时长统计 =============处理按钮点击事件  ");    }else if (controlEvents == UIControlEventTouchUpInside || controlEvents == UIControlEventTouchUpOutside){        /**         处理按钮松开状态         */        NSLog(@"aop_sendAction ============= 时长统计 =============处理按钮松开状态  ");    }    [self aop_sendActionsForControlEvents:controlEvents];}@end@implementation UIScrollView (AOP)#pragma mark - Setter And Getterstatic const char p_stopScrollBlock = '\0';- (StopScrollBlock)stopScrollBlock {    return objc_getAssociatedObject(self, &p_stopScrollBlock);}- (void)setStopScrollBlock:(StopScrollBlock)stopScrollBlock {    objc_setAssociatedObject(self, &p_stopScrollBlock, stopScrollBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}/**load初始化方法 代理方法互换*/+ (void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        Method originalMethod = class_getInstanceMethod([UIScrollView class], @selector(setDelegate:));        Method replaceMethod = class_getInstanceMethod([UIScrollView class], @selector(hook_setDelegate:));        method_exchangeImplementations(originalMethod, replaceMethod);    });}/* 1、快速滚动,自然停止; 2、快速滚动,手指按压突然停止; 3、慢速上下滑动停止 */#pragma mark - scrollView 停止滚动监测- (void)stopScroll:(UIScrollView *)scrollView {    if (self.stopScrollBlock) {        self.stopScrollBlock(scrollView);    }#ifdef DEBUG    NSLog(@"scrollView停止滚动了,存储日志上报服务器,可作为结束滑动钩子函数");#endif}#pragma mark - Hook UIScrollView setDelegate- (void)hook_setDelegate:(id
)delegate { [self hook_setDelegate:delegate]; if ([self isMemberOfClass:[UIScrollView class]]) { //Hook (scrollViewDidEndDecelerating:) 方法 Hook_Method([delegate class], @selector(scrollViewDidEndDecelerating:), [self class], @selector(p_scrollViewDidEndDecelerating:), @selector(add_scrollViewDidEndDecelerating:)); //Hook (scrollViewDidEndDragging:willDecelerate:) 方法 Hook_Method([delegate class], @selector(scrollViewDidEndDragging:willDecelerate:), [self class], @selector(p_scrollViewDidEndDragging:willDecelerate:), @selector(add_scrollViewDidEndDragging:willDecelerate:)); //Hook (scrollViewWillBeginDragging:) 方法 Hook_Method([delegate class], @selector(scrollViewWillBeginDragging:), [self class], @selector(p_scrollViewWillBeginDragging:), @selector(add_scrollViewWillBeginDragging:)); } //不是UIScrollView,不需要hook方法}#pragma mark - Hook_Method //OC中static修饰方法有什么用?static void Hook_Method(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL noneSel){ //原实例方法 Method originalMethod = class_getInstanceMethod(originalClass, originalSel); // 替换的实例方法 Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel); // 如果没有实现 delegate 方法,则手动动态添加 if (!originalMethod) { Method noneMethod = class_getInstanceMethod(replacedClass, noneSel); BOOL addNoneMethod = class_addMethod(originalClass, originalSel, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod)); if (addNoneMethod) { NSLog(@"******** 没有实现 (%@) 方法,手动添加成功!!",NSStringFromSelector(originalSel)); } return; } // 向实现 delegate 的类中添加新的方法 // 这里是向 originalClass 的 replaceSel(@selector(p_scrollViewDidEndDecelerating:)) 添加 replaceMethod BOOL addMethod = class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod)); if (addMethod) { //添加成功 NSLog(@"******** 实现了 (%@) 方法并成功 Hook 为 --> (%@)", NSStringFromSelector(originalSel), NSStringFromSelector(replacedSel)); // 重新拿到添加被添加的 method,这里是关键(注意这里 originalClass, 不 replacedClass), 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法 Method newMethod = class_getInstanceMethod(originalClass, replacedSel); // 实现交换 method_exchangeImplementations(originalMethod, newMethod); }else { // 添加失败,则说明已经 hook 过该类的 delegate 方法,防止多次交换。 NSLog(@"******** 已替换过,避免多次替换 --> (%@)",NSStringFromClass(originalClass)); } }// 已经实现需要hook的代理方法时,调用此处方法进行替换#pragma mark - Replace_Method- (void)p_scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self p_scrollViewDidEndDecelerating:scrollView]; // 停止类型1、停止类型2 BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating; if (scrollToScrollStop) { [scrollView stopScroll:scrollView]; }}- (void)p_scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { NSLog(@"%s", __func__); [self p_scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; if (!decelerate) { // 停止类型3 BOOL dragToDragStop = scrollView.tracking && !scrollView.dragging && !scrollView.decelerating; if (dragToDragStop) { [scrollView stopScroll:scrollView]; } }}- (void)p_scrollViewWillBeginDragging:(UIScrollView *)scrollView { NSLog(@"%s", __func__); [self p_scrollViewWillBeginDragging:scrollView];#ifdef DEBUG NSLog(@"开始监听到scrollView开始滑动,存储日志上报服务器");#endif}// 那没有实现需要hook的代理方法时,调用此处方法#pragma mark - Add_Method- (void)add_scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // 停止类型1、停止类型2 BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating; if (scrollToScrollStop) { [scrollView stopScroll:scrollView]; }}- (void)add_scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { NSLog(@"%s", __func__); if (!decelerate) { // 停止类型3 BOOL dragToDragStop = scrollView.tracking && !scrollView.dragging && !scrollView.decelerating; if (dragToDragStop) { [scrollView stopScroll:scrollView]; } }}- (void)add_scrollViewWillBeginDragging:(UIScrollView *)scrollView { NSLog(@"%s", __func__);#ifdef DEBUG NSLog(@"开始监听到scrollView开始滑动,存储日志上报服务器");#endif}@end

 

转载地址:http://iwcti.baihongyu.com/

你可能感兴趣的文章
DBCP——开源组件 的使用
查看>>
抓包工具
查看>>
海量数据相似度计算之simhash和海明距离
查看>>
DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)
查看>>
DeepLearning tutorial(6)易用的深度学习框架Keras简介
查看>>
DeepLearning tutorial(7)深度学习框架Keras的使用-进阶
查看>>
流形学习-高维数据的降维与可视化
查看>>
Python-OpenCV人脸检测(代码)
查看>>
python+opencv之视频人脸识别
查看>>
人脸识别(OpenCV+Python)
查看>>
6个强大的AngularJS扩展应用
查看>>
网站用户登录系统设计——jsGen实现版
查看>>
第三方SDK:讯飞语音听写
查看>>
第三方SDK:JPush SDK Eclipse
查看>>
第三方开源库:imageLoader的使用
查看>>
自定义控件:飞入飞出的效果
查看>>
自定义控件:动态获取控件的高
查看>>
第三方开源库:nineoldandroid:ValueAnimator 动态设置textview的高
查看>>
第三方SDK:百度地图SDK的使用
查看>>
Android studio_迁移Eclipse项目到Android studio
查看>>