struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
void setData(class_rw_t *newData) {
bits.setData(newData);
...//省略很多方法
//********************* 分割线 ******************************
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
...//省略很多方法
//********************* 分割线 ******************************
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
案例:有一个类Person,Person类有一个类方法walk,调用如下:
[Person walk];
Person对象在调用run的时候会依次进行方法的查找,此时的查找有分为快速查找(汇编cache_t查找)和慢速查找(C/C++查找),若找到则返回IMP,找不到则进行消息转发流程,如果消息转发没有处理,则直接Crash掉。
一.方法查找
在调用方法的地方打一个breakpoint,使用Debug下的Always Show Disassembly查看一下汇编源码如下:
objc_msgSend 利用runtime机制给对象发送消息的函数,_objc_msgSend之所以用汇编写是因为,写一个函数,保留未知的参数,跳转到任意的指针,参数是在运行时传递的,C是无法实现的并且也没有满足做这件事的必要性,还需要向底层编译,汇编有寄存器,可以直接进行保存、直接进行编译,比较快,汇编直接进行了一系列位移操作,直接操作内存,就非常快。全局搜索_objc_msgSend方法查看汇编源码,这里原则arm64进行分析。
如上图,可以看到进入_objc_msgSend之后,发现执行了LNilOrTagged,很明显是一个判断,判断是否为空,或者是否是Tagged Point类型,这个LNilOrTagged执行了什么呢?
LReturnZero 判断是否为nil,如果为空,直接就返回了,不会在进行方法的查找。LGetIsaDone 开始处理isa,外物皆对象,万物皆有isa。
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LGetIsaDone 加载完毕后调用了CacheLookup NORMAL,CacheLookup 主要是开始从缓存列表中查找。CacheLookup是一个宏,有三种情况,NORMAL|GETIMP|LOOKUP,而我们刚刚传入的是NORMAL。
CacheHit是一个宏,表示命中了,找到了imp,就会调用TailCallCachedImp返回imp。
如果没有找到,就会调用CheckMiss,CheckMiss也是一个宏,由于前面传递的是NORMAL,所以会调用__objc_msgSend_uncached。
开始调用__objc_msgSend_uncached,这个里面有一个非常重要的调用MethodTableLookup,在TailCallFunctionPointer之前调用,说明开始进行方法列表的的查找。
MethodTableLookup也是一个宏,methodList内部比较复发,里面存储的是methode_t,SEL 和 IMP以哈希表的方式存储。
此时搜索__class_lookupMethodAndLoadCache3已经查找不到了,这个时候我们就要考虑C/C++了,换一个思维,这里有一个”__“的区别。搜索“_class_lookupMethodAndLoadCache”就会发现找到了。
_class_lookupMethodAndLoadCache3这个方法返回的类型就是IMP。注意:上面3个参数:
cls:因为我们已经通过汇编已经编译到了我们所有的结构,如果没有编译到,上面就不会有isa了,也就是YES。
cache:如果有cache,上面就返回了,所以这个肯定是没有的,也就是NO。
resolver:动态解析,如果没有即将进入下一个阶段,动态方法解析和消息转发机制了。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { //进入到这里,传递的参数cache本身就是NO
imp = cache_getImp(cls, sel);
if (imp) return imp;
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) { //如果这个类没有实现,就去实现这个类,里面有class_rw_t
realizeClass(cls);
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
//重点!!!
retry:
runtimeLock.assertLocked();
// Try this class's cache.
//imp ?设计
//remap(cls) -- 重映射 一开始找的时候的的确确是有的,但是没有找到imp,我们在创建类的时候,哈希表会进行修复,对类进行重映射,可能就有了
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. //先从本类的方法列表中开始查找
Method meth = getMethodNoSuper_nolock(cls, sel); //for循环进行查找方法,返回Method
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls); //给我们的缓存赋值
imp = meth->imp;
goto done;
// Try superclass caches and method lists. //从父类的方法列表中查找
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass) //只要不是nil,因为上帝类NSObject最后指向了nil
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
// No implementation found. Try method resolver once. //如果都没有找到,就进行解析
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //当调用这个方法的时候,系统自动Call +resolveClassMethod or +resolveInstanceMethod.
runtimeLock.lock();
triedResolver = YES;
goto retry;
return imp;
二.动态方法解析
如果经过了一列操作,没有找到imp,就会进行动态方法解析_class_resolveMethod。
void _class_resolveMethod(Class cls, SEL sel, id inst)
if (! cls->isMetaClass()) { //判断是否是元类
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
_class_resolveInstanceMethod(cls, sel, inst);
Person类里面进行重写resolveInstanceMethod和resolveClassMethod。
重写了着两个方法以后,调用类方法walk的时候,打印了两次,意味着resolveClassMethod执行了两次,Why???
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
// Resolver not implemented.
return;
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); //系统帮你给对象发送了消息
首先看一下消息转发流程图:
第一次调用,是没有问题的,第二次调用是在消息无法处理以后又调用了一次。为了印证这个理论,在打印的地方打一个breakpoint,打印一下堆栈调用信息。
resolveClassMethod是在上面分析的_objc_msgSend_uncached方法之后执行了第一次调用,再接着往下运行。
第二次调用就是在methodSignatureForSelector,也就是第二次消息转发没有处理之后开始了resolveClassMethod这个方法第二次调用。
消息转发首先会进行动态方法解析resolveClassMethod,处理这个方法的处理很简单,对select进行方法的转移就可以了。这里对类方法进行重写,实例方法实现方式一样,就不粘贴代码了。
//重写resolveClassMethod方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(helloWord)) {
SEL helloWordSEL = @selector(helloWord);
Method helloWordMet = class_getClassMethod(self, helloWordSEL);
IMP helloWordIMP = method_getImplementation(helloWordMet);
const char *type = method_getTypeEncoding(helloWordMet);
return class_addMethod(self, helloWordSEL, helloWordIMP, type);
return [super resolveClassMethod:sel];
//自定义方法
+ (void)helloWord{
NSLog(@"%s",__func__);
当重写了resolveClassMethod以后,回到我们当初的lookUpImpOrForward方法内部。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //动态方法解析
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry; //回到了retry,重新查找方法
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache; //如果动态方法解析过程没有处理,就会进入到这里--消息转发
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
查看lookUpImpOrForward的源码发现,执行完动态方法解析或消息转发以后,又回到了retry进行方法的查找,刚刚在动态方法解析里面已经对类添加了方法,实现了select的转移,直接调用,不会再Crash了。
如果_class_resolveMethod动态方法没有进行处理,就会进行消息转发_objc_msgForward_impcache。这一块只有汇编调用,没有源码实现,源码实现闭源了。但是我们可以通过instrumentObjcMessageSends这个函数来打印底层调用了那些信息,最后会存储在private/tmp文件夹下。
#import <Foundation/Foundation.h>
#import "Student.h"
#include <objc/message.h>
extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[Person walk];
instrumentObjcMessageSends(NO);
return 0;
+ Person NSObject initialize
+ Person Person resolveClassMethod:
+ Person Person resolveClassMethod:
+ NSObject NSObject resolveClassMethod:
+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:
+ Person Person forwardingTargetForSelector:
+ Person Person forwardingTargetForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
+ NSObject NSObject forwardingTargetForSelector:
+ Person Person methodSignatureForSelector:
+ Person Person methodSignatureForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
首先实现第一步,forwardingTargetForSelector。
//重写forwardingTargetForSelector方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(run)) {
// 转发给我们的Student 对象
return [Student new];
return [super forwardingTargetForSelector:aSelector];
因此可以在forwardingTargetForSelector里面进行自定义处理,还可以进行一系列cash的手机、防止崩溃等。
如果forwardingTargetForSelector依然没有处理,那就开始处理消息签名,消息转发。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return [super methodSignatureForSelector:aSelector];
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//重定向
NSLog(@"%s",__func__);
NSString *sto = @"方法来了奥~";
anInvocation.target = [Student class];
[anInvocation setArgument:&sto atIndex:2];
NSLog(@"%@",anInvocation.methodSignature);
anInvocation.selector = @selector(run:);
[anInvocation invoke];
最后附上思维导图
?文章有点长,请做好心理准备...Let`s go! 基于Runtime机制,OC的对象发送消息就会通过一系列操作,根据对象从相应的类中查找方法对应的列表(类即类对象,方法存储在元类的方法列表中),方法列表实质是一个哈希表,通过SEL查找到IMP(即函数指针),返回相应的实现。struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__ Class _Nullable s...
iOS底层探索之Runtime(一):运行时&方法的本质
iOS底层探索之Runtime(二): objc_msgSend&汇编快速查找分析
在前面的文章中介绍了消息发送(objc_msgSend)流程,主要是汇编快速查找cache的过程,并对汇编源码进行了分析,本章内容主要分析慢速查找_lookUpImpOrForward流程。
2. _lookUpImpOrForward
在汇编的快速查找没有找到缓存,就会进入__objc_msgSend_uncached,在__objc_
通过之前博客的介绍,这个博客我们来介绍objc_msgSend,相信很多小伙伴在面试的时候,经常遇到面试官问:你知道runtime的消息机制吗?等等关于runtime的知识点,学会了runtime不止让我们面试中能增加亮点,也会为我们开发中提高很多便利!
通过这个博客你将学习到消息机制的三大阶段的消息发送具体是怎么执行的,详细的介绍底层甚至汇编语言执行的过程,详细仔细学习下来你会收获不少!好了,话不多说,lets begin!
objc_msgSend三大阶段:消息发送、动态方法解析、消息转发