美文网首页
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