您的位置:首页 > 编程语言 > ASP

Aspects 源代码解析<一>

2015-10-04 20:33 465 查看

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);
}


参数说明:

selectoroptionsblockerror
要被hook的selector切面(block)执行的时机,在selector执行前、后执行,还是替换被hook的selector切面hook过程中的错误
方法说明:

第一个方法为类方法,receiver为要被hook的类(
[receiver message]
)。直接在本类上hook类的实例方法(被KVO过的类,直接在KVO过程中生成的子类上进行hook实例方法),进行method swizzling,对类的methodLists
struct 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)
中进行的。

第二个方法为实例方法,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]
时,程序流程如下:



参考:

/article/5086208.html 简单理解AOP(面向切面编程)

http://blog.ibireme.com/2013/11/26/objective-c-messaging/ Objective-C 中的消息与消息转发

http://blog.cnbang.net/tech/2855/ JSPatch实现原理详解<二>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: