Aspects源码浅析

在不断填坑中前进。。

Posted by 三十一 on June 11, 2019

Aspects源码浅析

思维导图

Aspects 可以很方便的让我们 hook 要执行的方法,可以很方便的在方法执行前,执行后来执行我们的操作,也可以替换原方法的实现。

Delightful, simple library for aspect oriented programming in Objective-C and Swift.

实现原理简单的说就是,通过动态创建子类、动态修改子类方法的实现,将原类的类型指向子类,从而将原来的方法的实现指向了子类的方法实现,可以方便的观察方法的执行情况。跟系统实现 KVO 的原理一样。 π 比如 hook A 类的 xxx 方法,实现原理大致就是:

  1. 动态创建类 A 的子类 B (A__Aspects_)
  2. B 动态添加方法 aspects_xxx 方法,aspects_xxx 的实现指向原来的 xxx 方法
  3. B 替换 forwardInvocation: 实现为 __ASPECTS_ARE_BEING_CALLED__
  4. B 替换 原有的 xxx 实现为 _objc_msgForward
  5. A 的类型设置为 B
  6. 当调用 xxx 时,这个时候回 A 的类型已经被设置为 B ,会向 B 的方法列表里面查找 xxx 的实现。这个时候 xxxB 中已经被替换为 _objc_msgForward,进入消息转发流程,forwardInvocation:forwardInvocation:的实现被替换为__ASPECTS_ARE_BEING_CALLED__。最终会进入 __ASPECTS_ARE_BEING_CALLED__ 来处理。

关于消息转发的流程我这边在这里有一个简单的总结:Objective-C 中的消息与消息转发

简单使用

Aspects 的 API 封装的非常简洁,对外暴露了两个方法。

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

分别 hook 实例方法和类方法。 使用起来非常简单

 [testController aspect_hookSelector:@selector(viewWillDisappear:) withOptions:0 usingBlock:^(id<AspectInfo> info, BOOL animated) {
        UIViewController *controller = [info instance];
        if (controller.isBeingDismissed || controller.isMovingFromParentViewController) {
            [[[UIAlertView alloc] initWithTitle:@"Popped" message:@"Hello from Aspects" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
        }
    } error:NULL];

源码分析

Aspects hook 方法最终都会进入一个叫做 aspect_add 的方法。 aspect_add 大体做的事情有三件。

  1. aspect_performLocked 加锁保证线程安全。
  2. aspect_isSelectorAllowedAndTrack 判断是否允许 hook
  3. aspect_prepareClassAndHookSelector 动态创建原有类的子类和替换原有方法实现来达到 hook 的目的。

aspect_performLocked 就略去不谈,我们主要分析下下面两个方法的实现。

aspect_isSelectorAllowedAndTrack

1. 判断 hook 的方法是否为系统方法。

   static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    // Check against the blacklist.
    NSString *selectorName = NSStringFromSelector(selector);
    //判断是否是上面的几个方法,如果是,不跟踪。retain、release 等
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

判断是否是 @"retain", @"release", @"autorelease", @"forwardInvocation:" 方法,如果是这几个方法,返回 NO ,不进行 hook。

2. 判断 hook 类型。

通过位运算快速计算出 hook 类型。

    AspectOptions position = options&AspectPositionFilter;
#define AspectPositionFilter 0x07

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.
    
    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
     AspectPositionFilter == 0x07 转为为二进制位 0000 0000 0000 0111
     AspectOptions 选项值分别为 0128
     AspectOptionAutomaticRemoval = 1 << 3  就是1 的二进制左移3 变成 8

     这样可以快速的计算出当前选项值。
     
     转为二进制
     AspectPositionAfter            0000 0000 0000 0000
     AspectPositionInstead          0000 0000 0000 0001
     AspectPositionBefore           0000 0000 0000 0010
     AspectOptionAutomaticRemoval   0000 0000 0000 1000
     
    上面这个几个值 分别于 AspectPositionFilter 进行 & 位运算后等到值
     
     AspectPositionAfter          &AspectPositionFilter         0000 0000 0000 0000
     AspectPositionInstead        &AspectPositionFilter         0000 0000 0000 0001
     AspectPositionBefore         &AspectPositionFilter         0000 0000 0000 0010
     AspectOptionAutomaticRemoval &AspectPositionFilter         0000 0000 0000 0000

     
     if (aspect.options & AspectOptionAutomaticRemoval) 可以计算出 这里是为了计算出那些是运行后就移除的。
     
     AspectPositionAfter          &AspectPositionFilter             0000 0000 0000 0000
     AspectPositionInstead        &AspectPositionFilter             0000 0000 0000 0000
     AspectPositionBefore         &AspectPositionFilter             0000 0000 0000 0000
     AspectOptionAutomaticRemoval &AspectOptionAutomaticRemoval     0000 0000 0000 1000
     

3. 如果要 hook dealloc 方法。

如果要 hook dealloc 方法,只能在方法执行前 hook。

   if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }

4. 判断当前类是否相应要 hook 的方法

判断当前类是否相应要 hook 的方法,如果不响应,返回 NO

   if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }

5. 判断是否为类对象

如果不是类对象,就是实例对象,直接 返回 YES

class_isMetaClass(object_getClass(self))
     class_isMetaClass 判断是不是 MetaClass
     object_getClass 当参数传入的是一个实例对象的时候,返回类对象,当参数传入的是一个类对象的时候,返回一个元类对象(MetaClass
     class_isMetaClass
     Class cla1 = object_getClass([UIView new]);
     Class cla2 = object_getClass([UIView class]);
     
     NSLog(@" %@ %d",cla1,class_isMetaClass(cla1));//UIView 0
     NSLog(@" %@ %d",cla2,class_isMetaClass(cla2));//UIView 1
     
     class_isMetaClass(object_getClass(self) 就是判断 self 是不是类对象,如果是类对象进入 分支,如果是实例对象,返回 YES

如果是类对象,进入分支。

6.判断子类是否已经 hook 改方法

从全局的字典里面取出当前类相关联的 AspectTracker,判断子类是否已经 hook 过改方法。

   if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        //全局缓存的字典 key是class;value是AspectTracker实例
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        //判断子类是否已经 hook,如果子类已经 hook ,提示子类已经 hook了,返回 NO
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
            AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
            return NO;
        }
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
    return self.selectorNamesToSubclassTrackers[selectorName] != nil;
}

7. 判断父类是否已经 hook 改方法

do {
    //获取 AspectTracker
    tracker = swizzledClassesDict[currentClass];
    //判断 AspectTracker 已经 hook 的方法是否包含要 hook 的方法名
    if ([tracker.selectorNames containsObject:selectorName]) {
        //如果是当前类,返回 YES ,可以对当前类再次进行 hook。
        if (klass == currentClass) {
            // Already modified and topmost!
            return YES;
        }
        //不一致的话,返回 NO,报错已经 在 父类 HOOK 过了,保证一个继承链上只能 hook 一个类
        NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
        AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
        return NO;
    }
} while ((currentClass = class_getSuperclass(currentClass)));

判断继承链上向父类查找是否已经有类 hook 这个方法了。

 Class currentClass      = object_getClass([UITableViewCell class]);
 NSLog(@"currentClass == %@",currentClass);//UITableViewCell
 do {
    NSLog(@"class_getSuperclass == %@",currentClass);
    //UITableViewCell
    //UIView
    //UIResponder
    //NSObject
    //NSObject
 } while ((currentClass = class_getSuperclass(currentClass)));
            

当已经找到继承链的顶端后,class_getSuperclass 获取的数据为 null ,跳出循环。

8. 在当前类的继承链上标识 hook 关系。

如果当前类的继承链上都没有 hook 过改方法,在当前类的继承链上标识 hook 关系。

    do {
        //从全局缓存的字典中获取 AspectTracker
        tracker = swizzledClassesDict[currentClass];
        //如果 AspectTracker 为空
        if (!tracker) {
            // 根据 currentClass 生成 AspectTracker 实例,并缓存在全局的字典里
            tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
            swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
        }
        if (subclassTracker) {
            //如果 subclassTracker 存在 tracker 的根据 selectorName 存储的 set 中,添加subclassTracker
            [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
        } else {
            // 把 selectorName 放入已经 hook 的集合里面
            [tracker.selectorNames addObject:selectorName];
        }

        // All superclasses get marked as having a subclass that is modified.
        subclassTracker = tracker;
    }while ((currentClass = class_getSuperclass(currentClass)));

aspect_prepareClassAndHookSelector

aspect_prepareClassAndHookSelector 主要做了三件事情。

  1. aspect_hookClass 动态的创建 当前类的子类,并交换 forwardInvocation:class 方法的实现
  2. aspect_isMsgForwardIMP,判断当前调用的方法是否是 _objc_msgForward 的实现,如果是,跳过。
  3. 态的替换原方法的实现 class_addMethodclass_replaceMethod 动。

1. aspect_hookClass

动态的创建当前类的子类,并返回创建的子类,并交换 forwardInvocationclass 方法的实现。

1. 判断是否已经hook过

判断当前类是否已经被 hook 过

if ([className hasSuffix:AspectsSubclassSuffix]) {

如果已经hook 过,直接返回 object_getClass(self) 获取的类型,也就是动态创建的子类。

2. 判断是否是类对象

如果是类对象,aspect_swizzleForwardInvocation 替换 forwardInvocation 的实现为 __ASPECTS_ARE_BEING_CALLED__

3. 判断当前类名称是否一致

如果是 self.class object_getClass(self) 获取的类型不一致,有可能进行了 KVO 操作,aspect_swizzleForwardInvocation 替换 forwardInvocation 的实现为 __ASPECTS_ARE_BEING_CALLED__

4. 动态创建当前类的子类

如果上面的判断都不成立,就是一个实例对象,且没有被KVO。动态创建子类,原有类后面拼接 AspectsSubclassSuffix 作为新的类名,并且替换子类的 forwardInvocation:-(Class)class+(Class)class方法。

	const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
//创建 subclassName 的类,他的父类为 baseClass(原来的类)
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;
}

//交换子类的 ForwardInvocation 方法
aspect_swizzleForwardInvocation(subclass);
// 交换实例方法 class 方法的实现
aspect_hookedGetClass(subclass, statedClass);
// 交换类方法 class 方法的实现
aspect_hookedGetClass(object_getClass(subclass), statedClass);
//注册子类
objc_registerClassPair(subclass);
aspect_swizzleForwardInvocation

为新创建的子类替换 forwardInvocation: 实现,方法的实现指向 __ASPECTS_ARE_BEING_CALLED__。添加 __aspects_forwardInvocation:方法,方法的实现指向 __ASPECTS_ARE_BEING_CALLED__

static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    //替换 forwardInvocation 方法是实现
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        //将方法的实现 originalImplementation 对应的SEL设置为 AspectsForwardInvocationSelectorName
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
aspect_hookedGetClass

修改子类的 -(Class)class+(Class)class方法,使其返回父类的类型。

static void aspect_hookedGetClass(Class class, Class statedClass) {
    NSCParameterAssert(class);
    NSCParameterAssert(statedClass);
    //获取 class 方法的实现
	Method method = class_getInstanceMethod(class, @selector(class));
	IMP newIMP = imp_implementationWithBlock(^(id self) {
		return statedClass;
	});
	//替换class 方法的实现为  newIMP,返回 statedClass(父类) 的类型
	class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}
objc_registerClassPair

注册子类

5. 设置原有对象类型

self 的类型指向 动态创建的子类。

object_setClass(self, subclass);

2. aspect_isMsgForwardIMP

判断当前要 hook 的方法是否是 _objc_msgForward 或者 _objc_msgForward_stret

static BOOL aspect_isMsgForwardIMP(IMP impl) {
    return impl == _objc_msgForward
#if !defined(__arm64__)
    || impl == (IMP)_objc_msgForward_stret
#endif
    ;
}

3. 动态添加方法并且替换原方法的实现

动态添加方法并且替换原方法的实现

1. 获取原方法签名,为原方法添加前缀
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
static SEL aspect_aliasForSelector(SEL selector) {
    NSCParameterAssert(selector);
	return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
}
2. 动态添加方法

判断当前子类不响应新拼接的方法名。添加新创建的方法名到新类方法列表中。方法的实现指向原有的方法。

__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);

3.将原有放方法实现替换

将原有的方法实现 替换为 _objc_msgForward 。每次调用 这个方法,就进行了消息转发,相当用调用 forwardInvocation:,上面 forwardInvocation: 已经被替换为 __ASPECTS_ARE_BEING_CALLED__ 方法.

        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);

__ASPECTS_ARE_BEING_CALLED__

最终hook的方法会进入这个方法中。

1. 获取要调用方法名

//获取调用方法名
SEL originalSelector = invocation.selector;
//获取 交换后 的方法名
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);

2. 通过方法名获取要调用的方法的集合

//获取 实例消息发送者的集合
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//获取 类方法消息发送者 的集合
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);

3. 按顺序执行回调和方法

  1. 先便利 AspectsContainer 存储的 beforeAspects 数组,执行里面存储的 block
  2. 然后便利 insteadAspects 数组,执行里面存储的 block
  3. 如果 insteadAspects 数组为空,执行 aliasSelector 方法。aliasSelector 这个时候指向了原来方法的实现;
  4. 然后便利 afterAspects 数组,执行里面存储的 block
  5. block 执行的过程中,会判断类型是否为 AspectOptionAutomaticRemoval,如果是,将改选项加入到一个数组中,在方法执行后,解除 hook 关系;
  6. 如果没有找到 aliasSelector,通过 doesNotRecognizeSelector 抛出一个异常。

到这里,大致过了一遍 Aspects 的代码,但是这仅仅是粗略的过了一遍,还有很多细节可以继续扣下去。这里做个记录。

NSInvocation如何调用block