阅读 143

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

  1. 可以在这个方法中, 利用runtime进行方法添加

  2. 在方法查找,发现在方法列表查找不到时, 如果发现实现了这个方法, 就会执行这个方法并标记动态解析过了, 重新进入lookUpImpOrForward的流程, 并本次流程中不会再次动态解析

  3. 返回值只会影响系统的信息打印, 不影响转发流程 (建议还是跟随系统建议, 进行了方法添加返回YES)

- (id)forwardingTargetForSelector:(SEL)selector

消息转发流程中, 可以利用该方法, 将自己未实现的方法转发给其他对象 可以利用该方法配合protocol 来变相实现多继承

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

为了配合runtime,编译器将方法的返回类型和参数类型都编码成一个字符串,也就是method_ttypes.

在方法调用时,会转换成objc_msgsend函数,并在函数中传递消息接收者,消息对应的方法名字,以及真实的参数列表

那么就可以利用types来用合适的类型来接收参数,返回返回值

NSMethodSignature,就是对types的一种OC封装,可以方便的获取方法的参数个数,类型以及返回值类型, 跟method_tSELIMP无关, 只跟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


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐