Aspect

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关联一起。

  • (int)tapWithView:(double)pointx; => "i@:d"


    参数类型.png

[图片上传失败...(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容