前言

最近由于项目要求,前端移动组需要做一些用户行为的统计。但是由于一些早期的SDKs在最初设计时没有考虑到日志的功能,临时更改代码的成本也很高,架构组决定进行一次AOP开发实践来满足这个需求,用面向面的统计代替一些传统的代码嵌入打点。

AOP简介

AOP的全称是面向方面编程,中文翻译为Aspect Oriented Programming。AOP和面向对象编程是两种不同的领域设计思想。AOP的主要思想是提取业务处理中的方面,然后在这些方面上进行开发。

iOS中的AOP实践

由于Objective-C的运行时特性,实现一些看起来很Hack的行为非常方便,包括方法Swizzing。方法Swizzing的主要应用是交换两个已有的方法,使方法A在实际操作中实际执行方法B,而方法B在操作中执行方法A。

方法切换原理

要理解方法切换,您必须首先熟悉Objective-C中的消息转发机制,例如下面的代码:

编译器会将其翻译成以下代码来执行:

objc_msgSend,foo,5);

而具体的执行逻辑是:

1通过数组的isa指针找到它的类,比如这里的NSArray

2在非数组类的方法列表中查找插入对象:索引:方法

3一旦您找到方法insertObject:atIndex:,您将执行它的IMP实现

详见图:

从上述方法的数据结构可以看出,一个完整的方法主要由三部分组成,即sel方法名称、IMP方法实现、Method_Type方法参数和返回值,而@SELector的作用是获取该方法的SEL

在方法列表中,方法的SEL和IMP是一一对应的,如下图所示:

此时如果SEL1指向IMP2,SEL2指向IMP1,那么通过@selector得到的实现就是IMP2,只要能完成这个功能,就能实现方法的交换。

这就是方法Swizzling的原理,通过替换SEL对应的IMP来达到方法替换的目的

让我们看看如何在代码中实现这种行为:

//在类中交换originalSelector和swizzledSelector

+DRM _ swizzleMethod:cls original selector:original sel swizzledSelector:swizzledSel {

Class class = cls

//通过String获取对应的SEL

SEL original selector = nsselector from string;

SEL swizzledSelector = nsselectorFromString;

//获取对应的方法,Method SEL,用于获取IMP和TypeEcoding

方法original method = class _ GetInstanceMethod;

方法swizzledMethod = class _ GetInstanceMethod;

//若要开始交换,请将方法直接添加到当前类中。如果失败了,说明方法已经存在,可以直接交换

BOOL success = class _ addMethod,method _ getTypeEncoding);

if{

//如果添加成功,需要将swizzledmethod添加到当前类中,class_replaceMethod包含addMethod过程

class_replaceMethod,method _ getTypeEncoding);

} else {

method _ Exchangeimplementations;

}

}

当我们有了上面的方法,我们可以很容易地交换这两种方法:

+ 加载{

static dispatch _ once _ t once token;

dispatch _ once;

}

-Drmswizzled _ ViewDidappear:动画{

//注意这只是SEL,最终会指向viewDidAppear的IMP,不会形成无限循环

}

这样,当一个ViewController开始显示的时候,它会先调用drmswizzled _ viewdidear方法,然后通过回调原来的viewdidear,然后我们就可以有一个愉快的打点操作,可以在原来的方法执行之前或者之后进行,也可以得到原来方法的相应参数。

AOP在iOS上的再实践

如果只使用上面的方法Swizzling方法,可以在外面完成模块中的方法打点,但缺点是每个关键方法可能都需要Swizzle操作。如果方法太多,访问成本其实很高。然后我们细化需求,发现可以更快地监控常见的用户操作,如按钮点击和输入框中的文本更改。

原理解释

对于一个视图控制器实例,必须有一个根视图,在我们得到根视图后,我们可以遍历它的所有当前子视图,并一层一层递归地向下,然后我们可以得到当前视图控制器上的所有页面控件。

当我们获得一个UIControl时,我们可以从外部向它添加另一个触摸事件。

当我们得到一个UITextField时,我们可以从外部向它添加另一个文本更改事件。

...

这样,当我们遍历所有控件时,我们也同步了相应的方法来添加页面上所有需要监控的控件。

如果是这样,您可以快速点击并收听页面控件。

方法演示

讲了原理之后,我们来看看具体的监控方法是如何添加到每个控件中的:

@实现UIViewController

//首先你需要用方法Swizzling替换viewdiappear:view controller的方法

-Drmswizzled _ ViewDidappear:动画{

//调用原来的viewDidAppear:方法

//设置允许监控的类列表

NSArray * allowedClasses = @;

//首先判断当前类是否允许被监控

NSString * CurrentClass = nsstringFromClass;

if{

//开始递归查找所有页面元素

}

}

//递归子视图,查找所有子控件,开始添加UIControl或UITextField控件的监控

-recursiveSubViews:子视图{

for{

if{

}

else if{

}

else {

if {

}

}

}

}

//添加点击事件监控

-AddTouchEvent:控件{

}

//添加对文本框更改事件的监控

-addtextchangevent:textField {

}

//处理按钮所点击的事件,并点击

-OnContoltapped:发送者{

NSString * CLaSS name = NSStringFromClass;

NSString * senderTitle = nil

if{

senderTitle = ;

}

}

//处理文本框事件,点

-ontextfield changed:发件人{

NSString * CLaSS name = NSStringFromClass;

NSString * value = sender.text

nsString * title = sender . placeholder;

}

@end

以上可以快速减少Swizzle的代码量,也可以快速监控用户的实时行为,这也是GrowingIO前端数据不被入侵被埋没的主要逻辑。

性能和风险

1.使用方法切换可能会导致调试困难,并且问题不容易排除。但是,如果只是swizzle UIViewController中的一个didAppear方法,不会造成太多问题,swizzle也不会阻碍很多次,swizzle也不会增加太多开销。

2.遍历当前页面的控件会有非常小的性能开销,可以在这里进行优化。对于不再需要递归的单个控件,直接返回即可。

作者:于永凯,现就职于典融。com工程部,是iOS开发工程师。热爱互联网,乐于创造新事物。

1.《打点 前端iOS打点统计的AOP技术实践》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《打点 前端iOS打点统计的AOP技术实践》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/1665781.html