AspectiOS基础知识

Aspect

2018-08-23  本文已影响0人  StartBoy

AOP:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP对业务处理过程中的切面进行提取,他所面对的是处理过程中的某个步骤或阶段。以获得逻辑过程中各部分之间的耦合性的隔离效果。

OOP和AOP属于两个不同的“思考方式”。OOP专注于对象的属性和行为的封装,AOP专注于处理某个步骤和阶段的,从中进行切面的提取。

讲解:

Aspects是一个轻量级的面向切面编程的库。它能允许你在每一个类和每一个实例中存在的方法里面加入任何代码。可以在以下切入点插入代码:before(在原始的方法前执行) / instead(替换原始的方法执行) / after(在原始的方法后执行,默认)。通过Runtime消息转发实现Hook。Aspects会自动的调用super方法,使用method swizzling起来会更加方便。

Aspects 的方案就是,对于待 hook 的 selector,将其指向 objc_msgForward / _objc_msgForward_stret ,同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hook 住 forwardInvocation 函数,使他指向自己的实现。按照上面的思路,当被 hook 的 selector 被执行的时候,首先根据 selector 找到了 objc_msgForward / _objc_msgForward_stret ,而这个会触发消息转发,从而进入 forwardInvocation。同时由于 forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation 函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP。

swizzing forwardInvocation:
aspect_hookClass 函数主要swizzing类/对象的forwardInvocation函数。aspects的真正的处理逻辑都是在forwardInvocation函数里面进行的。对于对象实例而言,源代码中并没有直接 swizzling 对象的 forwardInvocation 方法,而是动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法(这里具体方法就是调用了 object_setClass(self, subclass) ,将当前对象 isa 指针指向了 subclass ,同时修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class。,这个地方特别绕,它的原理有点类似 kvo 的实现,它想要实现的效果就是,将当前对象变成一个 subclass 的实例,同时对于外部使用者而言,又能把它继续当成原对象在使用,而且所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在 remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象)。对于每一个对象而言,这样的动态对象只会生成一次,这里 aspect_swizzlingForwardInvocation 将使得 forwardInvocation 方法指向 aspects 自己的实现逻辑 ,

swizzling selector
当 forwradInvocation 被 hook 之后,接下来,将对传入的 selector 进行 hook ,这里的做法是,将 selector 指向了转发 IMP ,同时生成一个 aliasSelector ,指向了原来的 IMP ,同时为了防止重复 hook ,做了一个判断,如果发现 selector 已经指向了转发 IMP ,那就就不需要进行交换了

这里讲解下几个函数的用法

 // 添加方法
 /** 
 第一个参数: cls:给哪个类添加方法
 第二个参数: SEL name:添加方法的名称
 第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)
 第四个参数: types :方法类型,需要用特定符号,参考API
 */
 BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
 
 
 替换 class 的 sel 对应的函数指针,返回值为 sel 对应的原函数指针
 class_replaceMethod(Class cls, SEL name, IMP imp, const char * types) 
 
直接替换 method 的函数指针
method_setImplementation(Method method, IMP imp)

NSMethodSignature和 NSInvocation进行 method 或 block的调用
NSMethodSignature概述:
NSMethodSignature用于描述method的类型信息:返回值类型,以及每个参数的类型。
 //获取方法签名
-(void)test
{
    //获取方法签名
    NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr:)];
    //获取方法签名对应的invocation
    NSInvocation *invocationPrint = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
    //设置消息的接收者
    [invocationPrint setTarget:self];
    [invocationPrint setSelector:@selector(printStr:)];
    //设置参数
    //对NSInvocation对象设置的参数个数及类型和获取的返回值的类型要与创建对象时使用的NSMethodSignature对象代表的参数及返回值类型向一致,否则
    NSString *str = @"helloWord" ;
    [invocationPrint setArgument:&str atIndex:2];
    [invocationPrint invoke];


    //Block调用方式
    void(^block1)(int) = ^(int a)
    {
        NSLog(@"block1 %d",a);
    };
    
    NSMethodSignature *signature = aspect_blockMethodSignature(block1,nil);
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:block1];
    int a=2;
    //由block生成的NSInvocation对象的第一个参数是block本身,剩下的为 block自身的参数。
    [invocation setArgument:&a atIndex:1];
    [invocation invoke];
    

}
-(void)printStr:(NSString *)str
{
    NSLog(@"打印%@",str);
}
//代码来自 Aspect
// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;


static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        //NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        //AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        //AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

Objective-C Method 的 Type 信息以 “返回值 Type + 参数 Types” 的形式组合编码,还需要考虑到 self 和 _cmd 这两个隐含参数:

AspectInfo 类讲解

把外面传进来的实例instance,和原始的invocation保存到AspectInfo类对应的成员变量中。
- (NSArray *)arguments方法是一个懒加载,返回的是原始的invocation里面的aspects参数数组。

Type Encodings作为对RunTime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联一起。

[图片上传失败...(image-1d56c6-1534995690199)]
AspectInfo里面主要是 NSInvocation 信息。将NSInvocation包装一层,比如参数信息等。

AspectIdentifier讲解

AspectIdentifier是一个切片Aspect的具体内容。里面会包含了单个的 Aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等。初始化AspectIdentifier的过程实质是把我们传入的block打包成AspectIdentifier。

方法流程:

    - aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error
    └── aspect_add(self, selector, options, block, error);
        └── aspect_performLocked
            ├── aspect_isSelectorAllowedAndTrack
            └── aspect_prepareClassAndHookSelector
> 生成一个AspectIdentifier
> 获取到blockSignature

AspectsContainer讲解

按照切面的时机分别把切片Aspects放到对应的数组里面。removeAspects会循环移除所有的Aspects。

AspectTracker讲解

AspectTracker这个类是用来跟踪要被hook的类

objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
四个参数 源对象 关键字 关联的对象 关联策略 

五. Aspects hook过程详解

- aspect_prepareClassAndHookSelector(self, selector, error);
  ├── aspect_hookClass(self, error)
  │    ├──aspect_swizzleClassInPlace
  │    ├──aspect_swizzleForwardInvocation
  │    │  └──__ASPECTS_ARE_BEING_CALLED__
  │    │       ├──aspect_aliasForSelector
  │    │       ├──aspect_getContainerForClass
  │    │       ├──aspect_invoke
  │    │       └──aspect_remove
  │    └── aspect_hookedGetClass
  ├── aspect_isMsgForwardIMP
  ├──aspect_aliasForSelector(selector)
  └── aspect_getMsgForwardIMP
28_6.png

[图片上传失败...(image-f94b38-1534995690200)]
传送门
Demo注解: https://github.com/laotang013/AspectsDemo.git
https://halfrost.com/ios_aspect/
https://wereadteam.github.io/2016/06/30/Aspects/
https://www.jianshu.com/p/cd431dc5fd6e
https://www.jianshu.com/p/a6b675f4d073

上一篇下一篇

猜你喜欢

热点阅读