Aspects 是什么,解决了什么问题?
Aspects是AOP(面向切面编程)思想在iOS下OC的实现。Aspects可以用于hook函数,让函数执行一些副操作(打印调试信息、记录日志等)。切面可以简单理解为嵌入不同函数中的功能相同的操作(打印调试信息等),每类功能相同的操作可以抽取出一个切面。下面简要介绍OOP(面向对象编程)和AOP的概念和区别:
- OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
- AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
OOP实际上是对对象的属性和行为的封装,而AOP对于这点就无从谈起,但是AOP是处理某个步骤和阶段的,从中进行切面的提取,也就是说,如果几个或更多个逻辑过程中,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,这么说来可能太含蓄,如果说到权限判断,日志记录等,可能就明白了。如果我们单纯使用OOP,那么权限判断怎么办?在每个操作前都加入权限判断?日志记录怎么办?在每个方法里的开始、结束、异常的地方手动添加日志?所有,如果使用AOP就可以借助代理完成这些重复的操作,就能够在逻辑过程中,降低各部分之间的耦合了。二者扬长补短,互相结合最好。
Aspects的思路及实现
思路
Aspects对外暴露了两个接口,用于hook selector:
#pragma mark - Public Aspects API
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
/// @return A token which allows to later deregister the aspect.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
参数说明:
selector | options | block | error |
---|---|---|---|
要被hook的selector | 切面(block)执行的时机,在selector执行前、后执行,还是替换被hook的selector | 切面 | hook过程中的错误 |
方法说明:
第一个方法为类方法,receiver为要被hook的类(
[receiver message]
)。直接在本类上hook类的实例方法(被KVO过的类,直接在KVO过程中生成的子类上进行hook实例方法),进行method swizzling,对类的methodListsstruct objc_method_list **methodLists
进行修改,修改的方法有两个:- forwardInvocation:
forwardInvocation: 的IMP(方法实现)被替换为:__ASPECTS_ARE_BEING_CALLED__
,该函数内部具体执行被hook的selector和切入的操作,forwardInvocation: 的本来的IMP被保存在__aspects_forwardInvocation:
中。forwardInvocation:的method swizzling操作是在static Class aspect_hookClass(NSObject *self, NSError **error)
中进行的; - 被hook的selector。被hook的selector的IMP被替换为:
_objc_msgForward
/_objc_msgForward_stret
,该方法用于触发消息转发,被hook的selector的本来的IMP被保存在aliasSelector
中。被hook的selector 的method swizzling 是在static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error)
中进行的。
- forwardInvocation:
第二个方法为实例方法,receiver为要被hook的类的实例。用该方法hook实例方法较复杂,步骤简述如下:
- 新生成一个被hook类的子类,
- 利用isa swizzle将该实例的isa
Class isa
指向新生成的被hook类的子类, - 对被hook类的子类进行method swizzling,method swizzling的思路与上面类方法的method swizzling相同。
这里直接拿世健同学的例子来讲解吧:
@interface Target : NSObject
- (void)targetSelector;
@end
- (void)targetSelector
{
NSLog(@"%@'s original selector: %@", self, NSStringFromSelector(_cmd));
}
我们要对Target类的targetSelector进行hook,以方法二(实例方法)为例进行讲解,假定aTarget为Target类的一个实例方法。
Target *aTarget = [[Target alloc] init];
[aTarget aspect_hookSelector:NSSelectorFromString(@"targetSelector")
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"Hook targetSelector");
}
error:NULL];
[aTarget targetSelector];
hook之前aTarget及targetSelector的指向:
hook的过程:
hook Class(“isa swizzling”):
- 通过
statedClass = self.class
获取self本来的class(class方法被重写了,用来获取self被hook之前的Class(Target)。下文交代class方法是如何被重写的); - 通过
Class baseClass = object_getClass(self)
获取self的isa指针实际指向的class(self在运行时实际的class,表面上看这是一只皮鞋(statedClass),实际上这是一只刮胡刀(basedClass))。 - 如果baseClass(实际指向的class)已经是被hook过的子类,则返回baseClass。
- 如果baseClass是MetaClass或者被KVO过的Class,则不必再生成subClass,直接在其自身上进行method swizzling。
- 如果不是上述3. 、4. 所述情况,默认情况下需要对被hook的Class进行”isa swizzling”:
- 通过
subclass = objc_allocateClassPair(baseClass, subclassName, 0)
动态创建一个被hook类(Target
)的子类(Target_Aspects_
); - 然后对子类的
forwardInvocation:
进行method swizzling,替换为__ASPECTS_ARE_BEING_CALLED__
,进行消息转发时,实际执行的是__ASPECTS_ARE_BEING_CALLED__
中的方法; - 重写子类的获取类名的方法
class
,使其返回被hook之前的类的类名; - 将self(aTarget)的isa指针指向子类
Target_Aspects_
(object_setClass(self, subclass)
)。
- 通过
class被hook后的情况:
#pragma mark - Hook Class
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
hook selector:
- 用新生成的aspects__targetSelector指向selector的IMP;
- 将selector指向_objc_msgForward(或者_objc_msgForward_stret,与前者的区别为返回值的不同,该实现返回结构体?)。
selector 被hook后:
经过上面hook class和hook selector,Target的targetSelector就被hook进“切面”了。当调用[aTarget targetSelector]
时,程序流程如下:
参考:
http://www.cnblogs.com/jyh317/p/3834271.html 简单理解AOP(面向切面编程)
http://blog.ibireme.com/2013/11/26/objective-c-messaging/ Objective-C 中的消息与消息转发
http://blog.cnbang.net/tech/2855/ JSPatch实现原理详解<二>