美文网首页
iOS - Aspects实现原理解析

iOS - Aspects实现原理解析

作者: yuying | 来源:发表于2019-02-12 10:41 被阅读2次

    两个问题

    Aspects如何hook实例方法?

    Aspects如何hook类的实例方法?

    注:Aspects目前不支持hook类方法。

    流程图

    Aspects

    解答

    1. 替换对象的forwardInvocation方法为__ASPECTS_ARE_BEING_CALLED__,该方法会在之前/替换/之后调用传入的 block。
    2. 把要hook的selector的imp替换成_objc_msgForward,直接走消息转发流程。

    对象为实例
    如果hook的对象为实例,则动态创建一个父类为实例父类的class,替换forwordInvocation方法,重写该类和其元类的[xxx class]方法返回实例原本的父类。然后把实例的isa指向该类。(是不是有点像kvo?)

    对象为类
    如果hook的对象为类,则替换forwordInvocation方法。

    注意
    如果对象是kvo对象,也只替换forwordInvocation方法。

    疑问

    消息转发如果在前几步被拦截了会怎样?

    如果在resolveInstanceMethod阶段被拦截,即实现了resolveInstanceMethod方法,能够调用hook的方法;如果在forwardingTargetForSelector阶段被拦截,不会调用hook方法。


    resolveInstanceMethod阶段

    @implementation ViewController
    
    #pragma clang diagnostic ignored "-Wundeclared-selector"
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // hook method
        [self aspect_hookSelector:@selector(nonExistMethod) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) {
            NSLog(@"Hook nonExistMethod using before mode");
        } error:nil];
        
        // call a non-exist method
        [self performSelector:@selector(nonExistMethod)];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(nonExistMethod)) {
            class_addMethod([self class], sel, (IMP)nonExistMethodIMP, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    void nonExistMethodIMP(id self, SEL selector) {
        NSLog(@"IMP for nonExistMethod");
    }
    #pragma clang diagnostic pop
    
    @end
    
    AspectsDemo[89062:2505035] Hook nonExistMethod using before mode
    AspectsDemo[89062:2505035] IMP for nonExistMethod
    

    forwardingTargetForSelector阶段

    @implementation ViewController
    
    #pragma clang diagnostic ignored "-Wundeclared-selector"
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // hook method
        [self aspect_hookSelector:@selector(nonExistMethod) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) {
            NSLog(@"Hook nonExistMethod using before mode");
        } error:nil];
        
        // call a non-exist method
        [self performSelector:@selector(nonExistMethod)];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(nonExistMethod)) {
            return [FowardObject new];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    #pragma clang diagnostic pop
    
    @end
    
    AspectsDemo[89132:2506990] Aspects: Unable to find selector -[ViewController nonExistMethod].
    AspectsDemo[89132:2506990] IMP for nonExistMethod
    

    如果已经实现了forwardInvocation会怎样?

    不会走自己的forwardInvocation方法。

    Hook两个不同实例的相同selector会怎样?

    不会相互影响。

    @implementation FowardObject
    
    - (void)doSomething {
        NSLog(@"%@:doSomething", self);
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
       FowardObject *obj1 = [FowardObject new];
        [obj1 aspect_hookSelector:@selector(doSomething) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> info) {
            NSLog(@"Hook obj1(%@):doSomething using instead mode", obj1);
        } error:nil];
        
        FowardObject *obj2 = [FowardObject new];
        [obj2 aspect_hookSelector:@selector(doSomething) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> info) {
            NSLog(@"Hook obj2(%@):doSomething using instead mode", obj2);
        } error:nil];
        
        [obj1 doSomething];
        [obj2 doSomething];
        [obj1 doSomething];
    }
    
    @end
    
    AspectsDemo[94369:2658642] Hook obj1(<FowardObject_Aspects_: 0x6000011a9080>):doSomething using instead mode
    AspectsDemo[94369:2658642] Hook obj2(<FowardObject_Aspects_: 0x6000011b3450>):doSomething using instead mode
    AspectsDemo[94369:2658642] Hook obj1(<FowardObject_Aspects_: 0x6000011a9080>):doSomething using instead mode
    

    补充

    AspectTracker
    作用:追踪每个类hook的selector情况,确保一条继承链上只有一个类hook了这个方法。

    aspect_getSwizzledClassesDict方法里保存了一个字典static NSMutableDictionary *swizzledClassesDict,key为class,value为AspectTracker。用于aspect_isSelectorAllowedAndTrack && aspect_deregisterTrackedSelector方法。

    AspectIdentifier & AspectsContainer
    AspectIdentifier<AspectToken>: Tracks a single aspect.
    AspectsContainer:AspectIdentifier的容器,保存之前/替换/之后hook方法对应的AspectIdentifier。

    相关方法:aspect_getContainerForObjectaspect_getContainerForClassaspect_destroyContainerForObject
    __ASPECTS_ARE_BEING_CALLED__方法里调用对象的AspectsContainer 里保存的AspectIdentifier,调用hook方法。
    aspect_cleanupHookedClassAndSelector方法里记录对象是否还有hook方法,如果没有则清理对象。

    源码结构

    Aspects.png

    参考

    ps:本文是个人看Aspects源码的理解,如果有不当之处麻烦各位指正。

    相关文章

      网友评论

          本文标题:iOS - Aspects实现原理解析

          本文链接:https://www.haomeiwen.com/subject/ykzdsqtx.html