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. 将原方法指向消息转发逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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 ); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 转发处理逻辑进行二次转发
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 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 ; } |
工欲善其事必先利其器
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2019-05-29 大道至简:认识的过程要抽象到极致,行动的过程要简单有效
2019-05-29 系统的本质:系统的本质是信息核能量的流动通道和控制机制。
2019-05-29 热修复技术沉思:jspatch
2019-05-29 iOS 应用逆向工程分析流程图
2018-05-29 元类型与类型的区别
2018-05-29 Swift - what's the difference between metatype .Type and .self?
2018-05-29 swift类型操作规范