流程图:
yulingtianxia.com/blog/2016/0…
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);
}