oc 运行时消息转发的 aop
运行时的消息转发
https://www.cnblogs.com/feng9exe/p/10945712.html
其实在 objc-msg-x86_64.s 中包含了多个版本的 objc_msgSend
方法,它们是根据返回值的类型和调用者的类型分别处理的:
objc_msgSendSuper
:向父类发消息,返回值类型为id
objc_msgSend_fpret
:返回值类型为 floating-point,其中包含objc_msgSend_fp2ret
入口处理返回值类型为long double
的情况objc_msgSend_stret
:返回值为结构体objc_msgSendSuper_stret
:向父类发消息,返回值类型为结构体
面向对象的消息转发处理
Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。为接下来的完整的消息转发生成一个 NSMethodSignature对象。NSMethodSignature 对象会被包装成 NSInvocation 对象,forwardInvocation: 方法里就可以对 NSInvocation 进行处理了。
在上一篇中我们分析过了,在objc_msgSend函数查找IMP的过程中,如果在父类也没有找到相应的IMP,那么就会开始执行_class_resolveMethod方法,如果不是元类,就执行_class_resolveInstanceMethod,如果是元类,执行_class_resolveClassMethod。在这个方法中,允许开发者动态增加方法实现。这个阶段一般是给@dynamic属性变量提供动态方法的。
基于消息转发的 AOP
1. 将原方法指向消息转发逻辑
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); Class klass = aspect_hookClass(self, error); Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); if (!aspect_isMsgForwardIMP(targetMethodIMP)) { // 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. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } } // Will undo the runtime changes made. static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { NSCParameterAssert(self); NSCParameterAssert(selector); Class klass = object_getClass(self); BOOL isMetaClass = class_isMetaClass(klass); if (isMetaClass) { klass = (Class)self; } // Check if the method is marked as forwarded and undo that. Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); if (aspect_isMsgForwardIMP(targetMethodIMP)) { // Restore the original method implementation. const char *typeEncoding = method_getTypeEncoding(targetMethod); SEL aliasSelector = aspect_aliasForSelector(selector); Method originalMethod = class_getInstanceMethod(klass, aliasSelector); IMP originalIMP = method_getImplementation(originalMethod); NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); class_replaceMethod(klass, selector, originalIMP, typeEncoding); AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } // Deregister global tracked selector aspect_deregisterTrackedSelector(self, selector); // Get the aspect container and check if there are any hooks remaining. Clean up if there are not. AspectsContainer *container = aspect_getContainerForObject(self, selector); if (!container.hasAspects) { // Destroy the container aspect_destroyContainerForObject(self, selector); // Figure out how the class was modified to undo the changes. NSString *className = NSStringFromClass(klass); if ([className hasSuffix:AspectsSubclassSuffix]) { Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]); NSCAssert(originalClass != nil, @"Original class must exist"); object_setClass(self, originalClass); AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass)); // We can only dispose the class pair if we can ensure that no instances exist using our subclass. // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around. //objc_disposeClassPair(object.class); }else { // Class is most likely swizzled in place. Undo that. if (isMetaClass) { aspect_undoSwizzleClassInPlace((Class)self); } } } }
static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) { IMP msgForwardIMP = _objc_msgForward; #if !defined(__arm64__) // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id. // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783 // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4) Method method = class_getInstanceMethod(self.class, selector); const char *encoding = method_getTypeEncoding(method); BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B; if (methodReturnsStructValue) { @try { NSUInteger valueSize = 0; NSGetSizeAndAlignment(encoding, &valueSize, NULL); if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) { methodReturnsStructValue = NO; } } @catch (NSException *e) {} } if (methodReturnsStructValue) { msgForwardIMP = (IMP)_objc_msgForward_stret; } #endif return msgForwardIMP; }
2. hook 转发处理逻辑进行二次转发
static void aspect_swizzleForwardInvocation(Class klass) { NSCParameterAssert(klass); // If there is no method, replace will act like class_addMethod. //把forwardInvocation的IMP替换成__ASPECTS_ARE_BEING_CALLED__ //class_replaceMethod返回的是原方法的IMP IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@"); // originalImplementation不为空的话说明原方法有实现,添加一个新方法__aspects_forwardInvocation:指向了原来的originalImplementation,在__ASPECTS_ARE_BEING_CALLED__那里如果不能处理,判断是否有实现__aspects_forwardInvocation,有的话就转发。 if (originalImplementation) { class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); } AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass)); }
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { NSCParameterAssert(self); NSCParameterAssert(invocation); //获取原始的selector SEL originalSelector = invocation.selector; //获取带有aspects_xxxx前缀的方法 SEL aliasSelector = aspect_aliasForSelector(invocation.selector); //替换selector invocation.selector = aliasSelector; //获取实例对象的容器objectContainer,这里是之前aspect_add关联过的对象 AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); //获取获得类对象容器classContainer AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); //初始化AspectInfo,传入self、invocation参数 AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; NSArray *aspectsToRemove = nil; // Before hooks. //调用宏定义执行Aspects切片功能 //宏定义里面就做了两件事情,一个是执行了[aspect invokeWithInfo:info]方法,一个是把需要remove的Aspects加入等待被移除的数组中。 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 { 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) 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)]; } #define aspect_invoke(aspects, info) \ for (AspectIdentifier *aspect in aspects) {\ [aspect invokeWithInfo:info];\ if (aspect.options & AspectOptionAutomaticRemoval) { \ aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \ } \ } - (BOOL)invokeWithInfo:(id<AspectInfo>)info { NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; NSInvocation *originalInvocation = info.originalInvocation; NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; // Be extra paranoid. We already check that on hook registration. if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { AspectLogError(@"Block has too many arguments. Not calling %@", info); return NO; } // The `self` of the block will be the AspectInfo. Optional. if (numberOfArguments > 1) { [blockInvocation setArgument:&info atIndex:1]; } void *argBuf = NULL; //把originalInvocation中的参数 for (NSUInteger idx = 2; idx < numberOfArguments; idx++) { const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); if (!(argBuf = reallocf(argBuf, argSize))) { AspectLogError(@"Failed to allocate memory for block invocation."); return NO; } [originalInvocation getArgument:argBuf atIndex:idx]; [blockInvocation setArgument:argBuf atIndex:idx]; } [blockInvocation invokeWithTarget:self.block]; if (argBuf != NULL) { free(argBuf); } return YES; }
我思故我在