runtime09 - 消息发送与转发机制
method_t 内存结构
iOS14以前是big
iOS14以后编译期间就确定下来的,是small,在运行期添加的方法,是big
small的核心原理是: 利用方法实现的地址在程序中的偏移量是固定的,而且不会脱离当前库的地址范围,所以用32位记录偏移量即可,利用程序加载的地址 + offset来代替真实的地址, 可以减少method
占用的内存, 还可以优化以前rebase的时候, 需要修复method的真实地址所占用的时间
iOS14以后, 如果是对某方法执行了setImp
,不能直接用small来存(一方面运行期添加的方法, 是big类型的, 另一方面, 可能交换系统方法, 所以靠32位的offset是无法做到的),那么会有一个全局的map来管理, key是method_t的地址, value是imp
的值
struct method_t { struct big { SEL name; const char *types; MethodListIMP imp; }; struct small { RelativePointer<const void *> name; RelativePointer<const char *> types; RelativePointer<IMP> imp; } small &small() const { return *(struct small *)((uintptr_t)this & ~(uintptr_t)1); } big &big() const { ASSERT(!isSmall()); return *(struct big *)this; } } 复制代码
template <typename T> struct RelativePointer: nocopy_t { int32_t offset; // 这是一个 32 位有符号偏移量,提供 ±2GB 的范围。 T get() const { if (offset == 0) return nullptr; uintptr_t base = (uintptr_t)&offset; uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset; uintptr_t pointer = base + signExtendedOffset; return (T)pointer; } }; 复制代码
iOS14以后,对方法有个优化 详细看这里
方法缓存
即使是在父类方法列表中找的方法,最终也会缓存在自己的缓存中
+ (BOOL)resolveInstanceMethod:(SEL)sel
可以在这个方法中, 利用
runtime
进行方法添加在方法查找,发现在方法列表查找不到时, 如果发现实现了这个方法, 就会执行这个方法并标记动态解析过了, 重新进入
lookUpImpOrForward
的流程, 并本次流程中不会再次动态解析返回值只会影响系统的信息打印, 不影响转发流程 (建议还是跟随系统建议, 进行了方法添加返回YES)
- (id)forwardingTargetForSelector:(SEL)selector
消息转发流程中, 可以利用该方法, 将自己未实现的方法转发给其他对象 可以利用该方法配合protocol
来变相实现多继承
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
为了配合runtime
,编译器将方法的返回类型和参数类型都编码成一个字符串,也就是method_t
的types
.
在方法调用时,会转换成objc_msgsend函数,并在函数中传递消息接收者,消息对应的方法名字,以及真实的参数列表
那么就可以利用types
来用合适的类型来接收参数,返回返回值
NSMethodSignature
,就是对types
的一种OC封装,可以方便的获取方法的参数个数,类型以及返回值类型, 跟method_t
的SEL
和IMP
无关, 只跟types
有关
@interface NSMethodSignature : NSObject + (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types; @property (readonly) NSUInteger numberOfArguments; - (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER; @property (readonly) NSUInteger frameLength; - (BOOL)isOneway; @property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER; @property (readonly) NSUInteger methodReturnLength; @end 复制代码
- (void)forwardInvocation:(NSInvocation *)anInvocation
NSInvocation 像是对消息发送的一个oc封装, 非常灵活
可以利用NSInvocation, 来获取传入的真实参数
值, 改变某些传入的参数
, 改变消息接收者
, 设置改变要调用的SEL
, 设置改变返回值
可以利用NSInvocation
来做跨组件通信或者调用三方库的私有函数, 与-performSelector:
类似,但是能传递无限制的参数 (缺点: 要利用反射, 硬编码会比较多, 有额外风险)
@interface NSInvocation : NSObject + (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; @property (readonly, retain) NSMethodSignature *methodSignature; - (void)retainArguments; @property (readonly) BOOL argumentsRetained; @property (nullable, assign) id target; @property SEL selector; - (void)getReturnValue:(void *)retLoc; - (void)setReturnValue:(void *)retLoc; - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx; - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; - (void)invoke; - (void)invokeWithTarget:(id)target; @end 复制代码
源码
查找方法地址
NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); if (slowpath(!cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimeLock.lock(); checkIsKnownClass(cls); cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; // 在继承链, 从低到高的查找方法缓存和方法列表 for (unsigned attempts = unreasonableClassCount();;) { if (curClass->cache.isConstantOptimizedCache(/* strict */true)) { #if CONFIG_USE_PREOPT_CACHES imp = cache_getImp(curClass, sel); if (imp) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); #endif } else { // curClass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { imp = meth->imp(false); goto done; } // 如果直到根类都找不到方法, 那么标志imp为`forward_imp` if (slowpath((curClass = curClass->getSuperclass()) == nil)) { // No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } } // Halt if there is a cycle in the superclass chain. if (slowpath(--attempts == 0)) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. // 查找父类方法缓存 imp = cache_getImp(curClass, sel); // 如果发现`imp`是`foward_imp`, 代表要进行消息转发, 直接break if (slowpath(imp == forward_imp)) { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } if (fastpath(imp)) { // Found the method in a superclass. Cache it in this class. goto done; } } // No implementation found. Try method resolver once. // 方法动态解析 if (slowpath(behavior & LOOKUP_RESOLVER)) { // 标志已经进行过方法解析 behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } done: if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { #if CONFIG_USE_PREOPT_CACHES while (cls->cache.isConstantOptimizedCache(/* strict */true)) { cls = cls->cache.preoptFallbackClass(); } #endif // 缓存查找到的方法 log_and_fill_cache(cls, imp, sel, inst, curClass); } done_unlock: runtimeLock.unlock(); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; } 复制代码
方法动态解析
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); if (!lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForwardTryCache(inst, sel, cls, behavior); }
作者:潘森
链接:https://juejin.cn/post/7028111514217218055