Aspects 是什么?
是 iOS 上的一个轻量级 AOP 库。它利用 method swizzling 技术为已有的类或者实例方法添加额外的代码,它是著名框架 PSPDFKit (an iOS PDF framework that ships with apps like Dropbox or Evernote)的一部分。
怎么使用 Aspects
/// Adds a block of code before/instead/after the current `selector` for a specific class.+ (id)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 )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 使用的技术
在阅读 Aspects 源码之前需要一些 Runtime 的相应知识,可以参考我自己的一些博客。
Aspects 的代码
/// Adds a block of code before/instead/after the current `selector` for a specific instance.- (id)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)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;}复制代码
- 给 block 加锁
- 判断 selector 是否符合 hook 的规则
- 创建一个 AspectsContainer 对象,用 selector 关联到实例对象。用于管理一个对象或者类的一个方法的所有 aspects
- 创建一个 AspectIdentifier 对象,并放入 AspectsContainer 对象管理。AspectIdentifier 对象 表示一个 aspect 的内容
细看 aspect_isSelectorAllowedAndTrack 方法的内容,看如何判断一个 selector 是否符合 hook 规则
// 判断 selector 是否能被 hookstatic 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 的判断规则
- @"retain", @"release", @"autorelease", @"forwardInvocation:" 这些方法是不允许被 hook 的
- dealloc 方法不允许在执行之后被 hook
- 被 hook 的方法不存在于类中
- 一个方法只能被 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)); }}复制代码
- swizzling forwardInvocation。
- 拿到原始 selector 的 方法实现,再生成一个 aliasSelector 来指向原来 selector 的方法实现
- 把 selector 指向 _objc_msgForward 函数,用 _objc_msgForward 函数指针代替 selector 的 IMP ,这样执行 selector 的时候就会执行 _objc_msgForward 函数。
接下来看看 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)];}复制代码
- 根据切面的时机点,执行相应的代码
- 执行完额外的代码之后,看下是否需要回到原来的代码上,若是需要则执行原来的代码
- 若是没有找到被 hook 的 selector 的原始方法实现,那么进行消息转发
- Aspects 做对应的清理工作
Aspects 的思路
-
找到被 hook 的 originalSelector 的 方法实现
-
新建一个 aliasSelector 指向原来的 originalSelector 的方法实现
-
动态创建一个 originalSelector 所在实例的子类,然后 hook 子类的 forwardInvocation: 方法并将方法的实现替换成 ASPECTS_ARE_BEING_CALLED 方法
-
originalSelector 指向 _objc_msgForward 方法实现
-
实例的 originalSelector 的方法执行的时候,实际上是指向 objc_msgForward ,而 objc_msgForward 的方法实现被替换成 ASPECTS_ARE_BEING_CALLED 的方法实现,也就是说 originalSelector 的方法执行之后,实际上执行的是__ASPECTS_ARE_BEING_CALLED 的方法实现。而 aliasSelector 的作用就是用来保存 originalSelector 的方法实现,当 hook 代码执行完成之后,可以回到 originalSelector 的原始方法实现上继续执行。
参考
- https://github.com/steipete/Aspects
- https://wereadteam.github.io/2016/06/30/Aspects/
- https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
- 代码注释 https://github.com/junbinchencn/Aspects