浅谈AOP在Objective-C中的应用(一)
一、什么是AOP
Aspect Oriented Programming(面向切面编程)
--------通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
---------可应用于日志记录(埋点)、性能统计、安全控制、事务处理、异常处理等。
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
方法置换二、AOP实践
1. 页面埋点(曝光)
传统埋点:
-(void)viewWillDisappear:(BOOL)animated{
[superviewWillDisappear:animated];
[QMStatistic endLogPageView:@”page_tab” extendParams:vc.sta_curItem];
}-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[QMStatistic beginLogPageView:@”page_tab” extendParams: vc.sta_curItem];
}
AOP埋点,在BaseVC的load中置换以下方法:
-(void)viewWillAppear:(BOOL)animated;
-(void)viewWillDisappear:(BOOL)animated;
根据VC的类型来找到对应的pagename, 扩展参数从对应页面的categrory方法中获取。
(void)injectHookViewAppearMethods:(QMStatViewTarget*)statTarget {
Class cls = NSClassFromString(statTarget.className);
SEL method = @selector(viewWillAppear:);
[cls aspect_hookSelector:method withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
QMBaseVC *vc = aspectInfo.instance;
[QMStatistic beginLogPageView:@”page_tab” extendParams: vc.sta_curItem];
}error:nil];
}
对于一个VC复用多个埋点怎么去实现?
- (NSString *)extendParamsForMethod:(SEL)selector {
return self.sta_curItem;
}
可以传入SEL参数,然后根据SEL去返回对应参数。
2. 点击埋点
传统埋点:
-(void)onExchangeTapped:(UIButton *)button {
[QMStatistic event:@“click_exchange“ extendParams:self.goodsModel.goodsId]; [self.exchangeDetailRequest startRequest];
}
AOP埋点:
重写UIApplication分类的load方法,在load中置换以下方法:
-(BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
以按钮点击为例,点击事件可以通过替换UIApplication的sendAction:to:from:forEvent事件来实现AOP埋点。根据view的tag或者touch的location来判断在哪个视图内,从而进行相应的埋点。将埋点的事件ID、扩展参数等封装在自定义对象中传递,个别参数需要通过上下文获得的,通过特殊的方法在运行时动态获取。
新增一个UIApplication的分类,实现以下hook方法:
- (void)hookSendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { QMStatActionTarget *statTarget = [self statTargetForAction:action target:target];
NSString *eventId = statTarget.eventId;if (eventId.length == 0) {
if ([target respondsToSelector:@selector(eventIdForMethod:args:)]) {
eventId = [target eventIdForMethod:action args:@[sender]];
}
}
NSString *extendParams = [target extendParamsForEventMethod:action args:@[sender]]; [QMStatistic event:eventId extendParams:extendParams];
[self hookSendAction:action to:target from:sender forEvent:event];
}
然后通过方法置换把这个替换成系统的sendAction:to:from:forEvent方法。
3. 参数动态下发
基于AOP的运行时特点,我们可以通过后台下发埋点配置,以及Objective-C
Method Swizzling技术在不影响原业务逻辑的基础上插入埋点,来达到动态埋点的目的。
方法参数表三、AOP的问题
AOP看起来很美,但实际上有不少坑。比如:
1.子类覆盖父类的方法。
2.运行时消息转发函数冲突。
3.复杂埋点无法实现。
4.容易出现方法名错误。
对于这些问题,业界有不少的解决方案,在这里我抛砖引玉一下,如果有更好的方法也可以提出来:
对于第1点,可以通过减少类的层次,少用继承,多用扩展和协议来避免。
对于第2点,应检查第三方库是否重载消息转发函数,尽量避免冲突。
对于第3点,一般的页面埋点和点击埋点通过AOP埋点来处理,复杂的还是用原来的方式。
对于第4点,运行时检查类名、函数名。