Aspects源码浅析

Aspects 可以很方便的让我们 hook 要执行的方法,可以很方便的在方法执行前,执行后来执行我们的操作,也可以替换原方法的实现。
Delightful, simple library for aspect oriented programming in Objective-C and Swift.
实现原理简单的说就是,通过动态创建子类、动态修改子类方法的实现,将原类的类型指向子类,从而将原来的方法的实现指向了子类的方法实现,可以方便的观察方法的执行情况。跟系统实现 KVO 的原理一样。 π 比如 hook A 类的 xxx 方法,实现原理大致就是:
- 动态创建类 A的子类B(A__Aspects_)
- B动态添加方法- aspects_xxx方法,- aspects_xxx的实现指向原来的- xxx方法
- B替换- forwardInvocation:实现为- __ASPECTS_ARE_BEING_CALLED__。
- B替换 原有的- xxx实现为- _objc_msgForward
- 将 A的类型设置为B。
- 当调用 xxx时,这个时候回A的类型已经被设置为B,会向B的方法列表里面查找xxx的实现。这个时候xxx在B中已经被替换为_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 大体做的事情有三件。
- aspect_performLocked加锁保证线程安全。
- aspect_isSelectorAllowedAndTrack判断是否允许 hook
- 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 选项值分别为 0、1、2、8,
     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 主要做了三件事情。
- aspect_hookClass动态的创建 当前类的子类,并交换- forwardInvocation:、- class方法的实现
- aspect_isMsgForwardIMP,判断当前调用的方法是否是- _objc_msgForward的实现,如果是,跳过。
- 态的替换原方法的实现 class_addMethod。class_replaceMethod动。
1. aspect_hookClass
动态的创建当前类的子类,并返回创建的子类,并交换 forwardInvocation、class 方法的实现。
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. 按顺序执行回调和方法
- 先便利 AspectsContainer存储的beforeAspects数组,执行里面存储的block;
- 然后便利 insteadAspects数组,执行里面存储的block;
- 如果 insteadAspects数组为空,执行aliasSelector方法。aliasSelector这个时候指向了原来方法的实现;
- 然后便利 afterAspects数组,执行里面存储的block;
- block执行的过程中,会判断类型是否为- AspectOptionAutomaticRemoval,如果是,将改选项加入到一个数组中,在方法执行后,解除 hook 关系;
- 如果没有找到 aliasSelector,通过doesNotRecognizeSelector抛出一个异常。
到这里,大致过了一遍 Aspects 的代码,但是这仅仅是粗略的过了一遍,还有很多细节可以继续扣下去。这里做个记录。
