面向切面编程
AOP面向切面编程在后台开发中已经是一个老生常谈的话题了,如Spring这个框架是面向切面编程实现中尤为具有代表性的一个框架。
关于AOP的描述AOP_百度百科
这里的放一个关于切面作用的粗略图
在iOS 中 可以通过面相切面编程做很多事情,如在业务代码的外埋点。脱离方法实现的参数检验,在dealloc前去释放资源等。
iOS中可以通过hook方法去实现AOP。基于需求,开发了一个库SFAspect,用于实现iOS中面向切面编程,实现参考了Aspect库。SFAspect支持以下功能
- hook单个对象或类的所有实例对象
- 设置优先级
- 在hook方法中修改参数和返回值
- 删除hook
SFAspect的使用
安装
pod 'SFAspect'
使用
- hook单个对象实例方法
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
BOOL animated = NO;
NSInvocation *invocation = aspectModel.originalInvocation;
//参数从2开始,因为方法执行的时候隐式携带了两个参数:self 和 _cmd,self是方法调用者,_cmd是被调用f方法的sel
[invocation getArgument:&animated atIndex:2];
NSLog(@"准备执行viewWillAppear,参数animated的值为%d",animated);
//改变参数
animated = NO;
[invocation setArgument:&animated atIndex:2];
}];
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:0 withHookOption:(HookOptionAfter) withBlock:^(SFAspectModel *aspectModel, HookState state) {
BOOL animated = NO;
NSInvocation *invocation = aspectModel.originalInvocation;
//参数从2开始,因为方法执行的时候隐式携带了两个参数:self 和 _cmd,self是方法调用者,_cmd是被调用f方法的sel
[invocation getArgument:&animated atIndex:2];
NSLog(@"执行viewWillAppear后,参数animated的值为%d",animated);
//也可以通过invocation获取返回值,详情参考消息转发过程中NSInvocation的用法
}];
- hook单个对象的类方法
[self.vc hookSel:@selector(sayHiTo:withVCTitle:) withIdentify:@"3" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
NSLog(@"hook单个对象的类方法");
}];
- hook类的所有对象的实例方法
[SFHookViewController hookAllClassSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
BOOL animated = NO;
NSInvocation *invocation = aspectModel.originalInvocation;
[invocation getArgument:&animated atIndex:2];
NSLog(@"准备执行viewWillAppear,参数animated的值为%d",animated);
}];
- hook类所有对象的类方法
[SFHookViewController hookAllClassSel:@selector(sayHiTo:withVCTitle:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
BOOL animated = NO;
NSInvocation *invocation = aspectModel.originalInvocation;
[invocation getArgument:&animated atIndex:2];
NSLog(@"hook所有对象的类方法");
}];
- hook同一个方法,优先级不同,优先级越高,越先执行
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
}];
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:1 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
}];
- 移除hook
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
}];
//移除hook后hook里面的block不执行
[self.vc removeHook:@selector(viewWillAppear:) withIdentify:@"1" withHookOption:(HookOptionPre)];
- hook中 pre,after,around的区别
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
//pre是在方法前执行
NSLog(@"pre-准备执行viewWillAppear");
}];
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:0 withHookOption:(HookOptionAfter) withBlock:^(SFAspectModel *aspectModel, HookState state) {
//after是在方法前执行
NSLog(@"after-执行viewWillAppear后");
}];
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"3" withPriority:0 withHookOption:(HookOptionAround) withBlock:^(SFAspectModel *aspectModel, HookState state) {
//around是在方法前后执行
if(state == HookStatePre){
NSLog(@"around准备执行viewWillAppear");
}
if (state == HookStateAfter) {
NSLog(@"around-准备执行viewWillAppear");
}
}];
SFAspect实现原理
- 实现原理
- 对于单个对象的hook 和 类所有实例对象的hook
- 什么时候释放动态生成的子类
- 关于AspectContainer的释放
实现原理
对我们需要hook的方法,调用时强制使其进入消息转发流程。
大致流程如图
执行步骤如下:
1 对被hook的方法的原有实现存储起来(添加一个别名方法,并把实现存储到别名方法),把hook 需要添加的操作封装成一个SFAspectModel对象,并存储到类或元类(实例方法存储到类中,类方法存储到元类中)的关联对象SFAspectContainer中。SFAspectContainer的作用是存储一个类中所有的SFAspectModel,也就是存储所有的hook操作
2 将被hook的方法的实现设置为系统的转发函数_objc_msgForward,这样每一次调用被hook的方法的时候,都会进入消息转发流程。替换methodSignatureForSelector为自定义的方法sf_methodSignatureForSelector,forwardInvocation替换为自定义的sf_forwardInvocation方法。这样每次进入消息转发的流程就会调用自定义的sf_methodSignatureForSelector和sf_forwardInvocation函数
3执行到sf_forwardInvocation时候,在关联SFAspectContainer中取出被hook的方法对应的SFAspectModel对象。并根据HookOption执行SFAspectModel里面的block,也就是hook action中进行的操作。根据HookOption中是否存在HookOptionInstead去决定是否执行原有的方法实现(也就是调用步骤1中的别名方法)。
对于单个对象的hook 和 类所有实例对象的hook
在SFAspect中有两种hook。
一种是针对单个对象的hook,
-(BOOL)hookSel:(SEL)sel withIdentify:(NSString *)identify withPriority:(int)priority withHookOption:(HookOption)option withBlock:(HookBLock)block;
一种是针对类所有的对象的hook
+(BOOL)hookAllClassSel:(SEL)sel withIdentify:(NSString *)identify withPriority:(int)priority withHookOption:(HookOption)option withBlock:(HookBLock)block;
这两种hook的实现原理都是上述的那样,但是针对单个对象的hook,会动态生成一个子类。实现有点像KVO的实现。关系如下
然后对子类进行上述的3个步骤的操作。
对于单个对象hook,针对每个对象,都会动态生成一个子类。并把子类关联到元类的SubClass列表当中
对于针对所有对象的hook,是在原类上操作
什么时候释放动态生成的子类
上面提到,针对单个对象的hook,会动态生成一个子类,那这个子类在不需要用的时候,就应该销毁。销毁的时机有两个。
一个是在remove所有hook的时候,一个是在对象销毁的时候。
- 对于remove所有hook这种情况,在每一个执行remove的时候都会检测是否已经remove了所有Hook,如果已经remove了hook,就会将对象的类重置为原类,并调用objc_disposeClassPair注销类
if([NSStringFromClass([self class]) hasPrefix:SubClassPrefix]){
if (!object_isClass(self)) {
//如果是调用者是对象,且不存在任何hook方法,则删除类
//如果调用者不是对象,只能等下一次hook的时候才能清除对象
if(isClassMethod)
{
//如果符合上面条件的是类方法,则检测对象方法是否有被hook,没有hook的情况下删除类
SFAspectContainer *classcontainer = getHookActionContainer([self class]);
if(classcontainer.preArray.count == 0 && classcontainer.afterArray.count == 0 && classcontainer.insteadArray.count == 0 && classcontainer.arroundArray.count == 0){
//清除父类中的关联对象
NSMutableArray *array = objc_getAssociatedObject([self superclass], "subClassArray");
for (int i = 0; i < array.count; i++) {
SubClassModel *subClass = array[I];
if (subClass.subClass == [self class]) {
[array removeObject:subClass];
break;
}
}
Class class = [self class];
object_setClass(self, [self superclass]);
objc_disposeClassPair(class);
}
}else{
//如果符合上面条件的不是类方法,则检测类方法是否有被hook,没有hook的情况下删除类
SFAspectContainer *metaClasscontainer = getHookActionContainer(objc_getMetaClass(class_getName([self class])));
// SFAspectContainer *metaClasscontainer = getHookActionContainer([self class]);
if(metaClasscontainer.preArray.count == 0 && metaClasscontainer.afterArray.count == 0 && metaClasscontainer.insteadArray.count == 0 && metaClasscontainer.arroundArray.count == 0){
//清除父类中的关联对象
NSMutableArray *array = objc_getAssociatedObject([self superclass], "subClassArray");
for (int i = 0; i < array.count; i++) {
SubClassModel *subClass = array[I];
if (subClass.subClass == [self class]) {
[array removeObject:subClass];
break;
}
}
Class class = [self class];
object_setClass(self, [self superclass]);
objc_disposeClassPair(class);
}
}
}
- 对于一个是在对象销毁的去释放类这种情况,由于去监听一个对象释放后再去进行注销类的操作比较麻烦。所以把这一步操作放在了hookSel这个方法里面。在每一次hook的时候,都会调用
void clearNoUseSubClass(id object){
NSMutableArray *array = nil;
if([NSStringFromClass([object class]) hasPrefix:SubClassPrefix]){
objc_getAssociatedObject([object superclass], "subClassArray");
}
else{
objc_getAssociatedObject([object class], "subClassArray");
}
if (array == nil) {
objc_setAssociatedObject([object class], "subClassArray", [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return;
}else{
for (int i = 0; i < array.count; i++) {
SubClassModel *model = array[I];
if (model.object == nil && model.subClass != nil ) {
[array removeObject:model];
objc_disposeClassPair(model.subClass);
i = i-1;
}
}
}
}
这个函数的作用是检测一个原类是有有hookSel方法动态生成的子类,如果有,则检测子类对应的对象是否为空,如果为空,则销毁这个子类。
由上可知,如果不是通过removehook这种方式销毁子类,而是通过hookSel这种方式检测并销毁无用的子类的时候,内存中永远都会有一个动态生成的子类。但是由于动态生成的子类所占的内容空间几乎可以忽略不及,所以可以忽略。
关于AspectContainer的释放
关于AspectContainer这个类,这个类是用于存储关联hook的SFAspectModel的。由于是通过objc_setAssociatedObject方式去关联到类或是元类当中。所以当子类释放掉的时候,AspectContainer也会自动释放。对于原类的AspectContainer,会在执行remove掉所有的hook后会删除