Aspects 源代码解析<一>

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过程中的错误

方法说明:

  1. 第一个方法为类方法,receiver为要被hook的类([receiver message])。直接在本类上hook类的实例方法(被KVO过的类,直接在KVO过程中生成的子类上进行hook实例方法),进行method swizzling,对类的methodListsstruct objc_method_list **methodLists进行修改,修改的方法有两个:

    1. forwardInvocation:
      forwardInvocation: 的IMP(方法实现)被替换为:__ASPECTS_ARE_BEING_CALLED__,该函数内部具体执行被hook的selector和切入的操作,forwardInvocation: 的本来的IMP被保存在__aspects_forwardInvocation:中。forwardInvocation:的method swizzling操作是在static Class aspect_hookClass(NSObject *self, NSError **error)中进行的;
    2. 被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)中进行的。
  2. 第二个方法为实例方法,receiver为要被hook的类的实例。用该方法hook实例方法较复杂,步骤简述如下:

    1. 新生成一个被hook类的子类,
    2. 利用isa swizzle将该实例的isa Class isa指向新生成的被hook类的子类,
    3. 对被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之前aTarget及targetSelector的指向

hook的过程:

hook Class(“isa swizzling”):
  1. 通过statedClass = self.class获取self本来的class(class方法被重写了,用来获取self被hook之前的Class(Target)。下文交代class方法是如何被重写的);
  2. 通过Class baseClass = object_getClass(self)获取self的isa指针实际指向的class(self在运行时实际的class,表面上看这是一只皮鞋(statedClass),实际上这是一只刮胡刀(basedClass))。
  3. 如果baseClass(实际指向的class)已经是被hook过的子类,则返回baseClass。
  4. 如果baseClass是MetaClass或者被KVO过的Class,则不必再生成subClass,直接在其自身上进行method swizzling。
  5. 如果不是上述3. 、4. 所述情况,默认情况下需要对被hook的Class进行”isa swizzling”:
    1. 通过subclass = objc_allocateClassPair(baseClass, subclassName, 0)动态创建一个被hook类(Target)的子类(Target_Aspects_);
    2. 然后对子类的forwardInvocation:进行method swizzling,替换为__ASPECTS_ARE_BEING_CALLED__,进行消息转发时,实际执行的是__ASPECTS_ARE_BEING_CALLED__中的方法;
    3. 重写子类的获取类名的方法class,使其返回被hook之前的类的类名;
    4. 将self(aTarget)的isa指针指向子类Target_Aspects_object_setClass(self, subclass))。

class被hook后的情况:
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:
  1. 用新生成的aspects__targetSelector指向selector的IMP;
  2. 将selector指向_objc_msgForward(或者_objc_msgForward_stret,与前者的区别为返回值的不同,该实现返回结构体?)。

selector 被hook后:

selector 被hook后

经过上面hook class和hook selector,Target的targetSelector就被hook进“切面”了。当调用[aTarget 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实现原理详解<二>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值