Aspects 是 iOS 上的一个轻量级 AOP 库。它利用 method swizzling 技术为已有的类或者实例方法添加额外的代码,它是著名框架 PSPDFKit (an iOS PDF framework that ships with apps like Dropbox or Evernote)的一部分。
- /// Adds a block of code before/instead/after the current `selector` for a specific class.
- + (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;
Aspects 提供了2个 AOP 方法,一个用于类,一个用于实例。在确定 hook 的 方法之后, Aspects 允许我们选择 hook 的时机是在方法执行之前,还是方法执行之后,甚至可以直接替换掉方法的实现。Aspects 的常见使用情景是 log 和 打点统计 等和业务无关的操作。比如 hook ViewController 的 viewWillLayoutSubviews 方法。
- [aspectsController aspect_hookSelector:@selector(viewWillLayoutSubviews) withOptions:0 usingBlock:^{
- NSLog(@"Controller is layouting!");
- } error:NULL];
在阅读 Aspects 源码之前需要一些 Runtime 的相应知识,可以参考我自己的一些博客。
- /// 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;
接下来的源码解读,主要是分析 Aspects 的 实例方法的执行流程,以及 Aspects 的设计思路。至于 Aspects 的类方法的执行流程和思路也是大同小异,这里就不再累赘了。
- /// @return A token which allows to later deregister the aspect.
- - (id<AspectToken>)aspect_hookSelector:(SEL)selector
- withOptions:(AspectOptions)options
- usingBlock:(id)block
- error:(NSError **)error {
- return aspect_add(self, selector, options, block, error);
- }
该方法返回一个 AspectToken 对象,这个对象主要是 aspect 的唯一标识符。 该方法调用了 static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) 方法,这个方法用于给一个实例添加 aspect 。
- static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * *error) {
- // ...... 省略代码
- __block AspectIdentifier * identifier = nil;
- // 给 block 加锁
- aspect_performLocked( ^ {
- // 判断 selector 是否可以被 hook
- if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
- // 创建一个 AspectsContainer 对象,用 selector 关联到实例对象
- AspectsContainer * aspectContainer = aspect_getContainerForObject(self, selector);
- // 创建一个 AspectIdentifier 对象,
- identifier = [AspectIdentifier identifierWithSelector: selector object: self options: options block: block error: error];
- if (identifier) {
- // 把 AspectIdentifier 对象加入 AspectsContainer 对象中
- [aspectContainer addAspect: identifier withOptions: options];
- // Modify the class to allow message interception.
- aspect_prepareClassAndHookSelector(self, selector, error);
- }
- }
- });
- return identifier;
- }
细看 aspect_isSelectorAllowedAndTrack 方法的内容,看如何判断一个 selector 是否符合 hook 规则
- // 判断 selector 是否能被 hook
- static BOOL aspect_isSelectorAllowedAndTrack(NSObject * self, SEL selector, AspectOptions options, NSError * *error) {
- // 不能被 hook 的方法集合
- static NSSet * disallowedSelectorList;
- static dispatch_once_t pred;
- dispatch_once( & pred, ^{
- // 这些方法不能被 hook
- disallowedSelectorList = [NSSet setWithObjects: @"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
- });
- // Check against the blacklist.
- // ...... 省略代码
- // Additional checks.
- AspectOptions position = options & AspectPositionFilter;
- // dealloc 方法不允许在执行之后被 hook,因为对象会被销毁
- if ([selectorName isEqualToString: @"dealloc"] && position != AspectPositionBefore) {
- // ...... 省略代码
- }
- // 被 hook 的方法不存在于类中
- if (! [self respondsToSelector: selector] && ![self.class instancesRespondToSelector: selector]) {
- // ...... 省略代码
- }
- // Search for the current class and the class hierarchy IF we are modifying a class object
- if (class_isMetaClass(object_getClass(self))) {
- Class klass = [self class];
- NSMutableDictionary * swizzledClassesDict = aspect_getSwizzledClassesDict();
- Class currentClass = [self class];
- AspectTracker * tracker = swizzledClassesDict[currentClass];
- // 判断子类是否已经 hook 该方法
- if ([tracker subclassHasHookedSelectorName: selectorName]) {
- // ...... 省略代码
- }
- do {
- // 判断是否已经 hook 了该方法
- tracker = swizzledClassesDict[currentClass];
- if ([tracker.selectorNames containsObject: selectorName]) {
- if (klass == currentClass) {
- // Already modified and topmost!
- return YES;
- }
- 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 )));
- // ...... 省略代码
- return YES;
- }
selector 不允许被 hook 的判断规则
接下来看看 static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) 方法实现。
- static void aspect_prepareClassAndHookSelector(NSObject * self, SEL selector, NSError * *error) {
- NSCParameterAssert(selector);
- Class klass = aspect_hookClass(self, error); // 1 swizzling forwardInvocation
- // 被 hook 的 selector
- Method targetMethod = class_getInstanceMethod(klass, selector);
- IMP targetMethodIMP = method_getImplementation(targetMethod);
- if (!aspect_isMsgForwardIMP(targetMethodIMP)) { //2 swizzling method
- // 使用一个 aliasSelector 来指向原来 selector 的方法实现
- // Make a method alias for the existing method implementation, it not already copied.
- const char * typeEncoding = method_getTypeEncoding(targetMethod);
- SEL aliasSelector = aspect_aliasForSelector(selector);
- if (! [klass instancesRespondToSelector: aliasSelector]) {
- __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
- NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
- }
- // We use forwardInvocation to hook in.
- // 把 selector 指向 _objc_msgForward 函数
- // 用 _objc_msgForward 函数指针代替 selector 的 imp,然后执行这个 imp
- class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
- AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
- }
- }
接下来看看 static Class aspect_hookClass(NSObject *self, NSError **error) 的实现
- static Class aspect_hookClass(NSObject * self, NSError * *error) {
- NSCParameterAssert(self);
- Class statedClass = self.class;
- Class baseClass = object_getClass(self);
- NSString * className = NSStringFromClass(baseClass);
- // 是否有 _Aspects_ 后缀
- // Already subclassed
- if ([className hasSuffix: AspectsSubclassSuffix]) {
- return baseClass;
- // We swizzle a class object, not a single object.
- } else if (class_isMetaClass(baseClass)) {
- return aspect_swizzleClassInPlace((Class) self);
- // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
- } else if (statedClass != baseClass) {
- return aspect_swizzleClassInPlace(baseClass);
- }
- // 动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法
- // Default case. Create dynamic subclass.
- const char * subclassName = [className stringByAppendingString: AspectsSubclassSuffix].UTF8String;
- Class subclass = objc_getClass(subclassName);
- if (subclass == nil) {
- // 生成 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);
- // 修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class。
- aspect_hookedGetClass(subclass, statedClass);
- aspect_hookedGetClass(object_getClass(subclass), statedClass);
- objc_registerClassPair(subclass);
- }
- // 将当前对象 isa 指针指向了 subclass
- // 将当前 self 设置为子类,这里其实只是更改了 self 的 isa 指针而已
- object_setClass(self, subclass);
- return subclass;
- }
该方法的作用是动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法,这样做的好处是不需要去更改对象本身的实例。该方法调用了static void aspect_swizzleForwardInvocation(Class klass) 方法对子类的 forwardInvocation: 方法进行混写;
接下来看看 static void aspect_swizzleForwardInvocation(Class klass) 的方法实现,看它如何实现对 forwardInvocation: 方法的混写
- //swizzling forwardinvation 方法
- static NSString * const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
- static void aspect_swizzleForwardInvocation(Class klass) {
- NSCParameterAssert(klass);
- // If there is no method, replace will act like class_addMethod.
- // 使用 __ASPECTS_ARE_BEING_CALLED__ 替换子类的 forwardInvocation 方法实现
- // 由于子类本身并没有实现 forwardInvocation ,
- // 所以返回的 originalImplementation 将为空值,所以子类也不会生成 AspectsForwardInvocationSelectorName 这个方法
- IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation: ), (IMP) __ASPECTS_ARE_BEING_CALLED__, "v@:@");
- if (originalImplementation) {
- NSLog(@"class_addMethod");
- class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
- }
- AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
- }
关键实现在在这句代码,将 forwardInvocation: 的实现换成 __ASPECTS_ARE_BEING_CALLED__实现
- IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation: ), (IMP) __ASPECTS_ARE_BEING_CALLED__, "v@:@");
到这里我们可以知道了,知道 hook 了一个方法,那么最后都会执行 ASPECTS_ARE_BEING_CALLED 这个方法,代码执行到这里基本就到末尾了。我们看看这个方法实现
- // This is the swizzled forwardInvocation: method.
- static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
- // ... 省略代码
- // Before hooks. 在切面之前执行
- aspect_invoke(classContainer.beforeAspects, info);
- aspect_invoke(objectContainer.beforeAspects, info);
- // Instead hooks. 替换切面
- BOOL respondsToAlias = YES;
- if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
- aspect_invoke(classContainer.insteadAspects, info);
- aspect_invoke(objectContainer.insteadAspects, info);
- }else {
- // 重新转回原来的 selector 所指向的函数
- Class klass = object_getClass(invocation.target);
- do {
- if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
- [invocation invoke];
- break;
- }
- }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
- }
- // After hooks. 在切面之后执行
- aspect_invoke(classContainer.afterAspects, info);
- aspect_invoke(objectContainer.afterAspects, info);
- // If no hooks are installed, call original implementation (usually to throw an exception)
- // 找不到 aliasSelector 的方法实现,也就是没有找到被 hook 的 selector 的原始方法实现,那么进行消息转发
- if (!respondsToAlias) {
- invocation.selector = originalSelector;
- SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
- if ([self respondsToSelector:originalForwardInvocationSEL]) {
- ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
- }else {
- [self doesNotRecognizeSelector:invocation.selector];
- }
- }
- // Remove any hooks that are queued for deregistration.
- [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
- }
来源: https://juejin.im/post/5a2fd67b5188253e2470f2f8