iOS 底层--Class探索和方法执行过程

1、 类(Class)结构

在源码中查看类信息

⚠️:错误跟踪: 在我们开发的工程中,通过command 跟踪进去的class 会进入到 runtime.h中的struct objc_class, 这里的是错误的,不是我们要的信息

⚠️:正确跟踪:在源码中,通过跟踪进入的class才是对的,最终在 objc-runtime-new.h 中 ,正确的应该是 继承objc_object 的结构体,如下:

struct objc_class : objc_object {
    // Class ISA;                  // 这个信息来源于 继承 objc_object
    Class superclass;          //  内存 8位
    cache_t cache;             //  内存 16位
    class_data_bits_t bits; 

    class_rw_t *data() {         // 非常重要的数据
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        assert((set & clear) == 0);
        data()->changeFlags(set, clear);
    }
   ……… 
}

类对象继承 objc_object

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

在看看非常重要的数据信息 class_rw_t *data()中的信息

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;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
    ........
};

问题1:为什么实例对象的方法要存在类对象中?
想象一下,如果每生成一个实例都会将所有的方法实现拷贝过去,那将会占用很大的内存,所以类生成实例的时候将实例的isa指向自己,调用函数时在isa指向的类中去执行该调用哪个方法的逻辑。


2、属性、变量、方法存储在哪?

1、结构方式探索 (控制台打印)

在上面 struct class_rw_t结构里有一个备注
方法列表、属性列表、协议列表存储在 const class_ro_t *ro
我们可以通过打印来验证这些信息:

在官网 中下载objc4-xxx 的源码,整成可编译运行的工程
或者去这里直接下载可运行的工程(也有方法介绍)

//新建一个JEPerson类
@interface JEPerson : NSObject
{
    NSString    *_jeName;
}
@property (nonatomic, copy) NSString *jeNick;
@end

// 在main中 初始化这个类
Class cls = NSClassFromString(@"JEPerson");

通过x指令打印cls信息 一步一步找到最后的信息

找成员/属性位置

x/5gx cls
0x1000021b0: 0x0000000100002188   (isa)   0x0000000100332140 (superclass)
0x1000021c0: 0x000000010032c490 0x0000002400000000  (这16位 是 cache_t ,结构体所占内存大小计算方法 可以去看之前的文章)
0x1000021d0: 0x0000000101816390    (bits)

// 拿到bits的地址在p
p 0x1000021d0
(long) $4 = 4294975952    

//这个$4不是我们想要的 ,通过强转 再p
p (class_data_bits_t *)0x1000021d0
(class_data_bits_t *) $5 = 0x00000001000021d0

//想要得到data()里面的信息,bits.data() 的方法
p $5->data()
(class_rw_t *) $6 = 0x0000000100720450

//查看$6里面有那些信息 
p *$6
(class_rw_t) $7 = {
  flags = 2148007936
  version = 0
  witness = 0
  ro = 0x0000000100002080
  methods = {
    list_array_tt = {
       = {
        list = 0x00000001000020c8
        arrayAndFlag = 4294975688
      }
    }
  }
  properties = {
    list_array_tt = {
       = {
        list = 0x0000000100002160
        arrayAndFlag = 4294975840
      }
    }
  }
  protocols = {
    list_array_tt = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}


// 查看ro里面的信息
p $6->ro
(const class_ro_t *) $7 = 0x0000000100002080

//看看$7是个什么东西
p *$7
(const class_ro_t) $8 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f4f "\x02"
  name = 0x0000000100000f46 "JEPerson"
  baseMethodList = 0x00000001000020c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002118
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002160
  _swiftMetadataInitializer_NEVER_USE = {}
}

// 查看对象的信息
p $8.ivars
(const ivar_list_t *const) $9 = 0x0000000100002118

//查看$9的信息
p *$9
(const ivar_list_t) $9 = {
  entsize_list_tt = {
    entsizeAndFlags = 32
    count = 2      //成员变量 2个
    first = {
      offset = 0x0000000100002178
      name = 0x0000000100000f51 "_jeName"
      type = 0x0000000100000f81 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}


//  有$8之后  可以p出属性
p $7.baseProperties
(property_list_t *const) $12 = 0x0000000100002160

//查看$12信息
p *$12
(property_list_t) $13 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1        // 属性数量
    first = (name = "jeNick", attributes = "T@\"NSString\",C,N,V_jeNick")
  }
}

这里就得到了成员变量的信息 count = 2 第一个成员变量是 first={ _jeName }
属性的信息 count = 1 第一个成员变量是 first={ jeNick }

找方法存储位置

(lldb) x/5gx cls
0x1000022f0: 0x00000001000022c8 0x0000000100002340
0x100002300: 0x000000010032c490 0x0000002c00000000
0x100002310: 0x0000000101304180

(lldb) p 0x100002310
(long) $1 = 4294976272

(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100002310

(lldb) p *$2
(class_data_bits_t) $4 = (bits = 4314907008)

(lldb) p $2->data()
(class_rw_t *) $5 = 0x0000000101304180

(lldb) p $5->ro
(const class_ro_t *) $6 = 0x0000000101304280

(lldb) p $6->baseMethodList
(method_list_t *const) $7 = 0x00000001000020c8

(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "readBook"
      types = 0x0000000100000f87 "v16@0:8"
      imp = 0x0000000100000cd0 (KCObjcTest`-[JEStudent readBook])
    }
  }
}

// 找到其他的方法
p $8.get(1)
p $8.get(2)
p $8.get(3)
.......

2、代码打印

//打印成员变量/属性
void testObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //获取实例变量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //获取属性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //获取属性值
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

// 打印方法列表
// 如果传入类   就是打印的实例方法/静态方法
// 如果传入元类 就打印的是类方法
void testObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

void testInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

void testClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

void testIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

3、方法

在调试过程中,最后打印出来的方法 包含三个信息nametypesimp,我们查看objc_method源码查看结构

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
} 

分别代表:方法名、方法类型(方法编码)、方法实现
方法名很简单,这里我们来研究方法类型(方法编码)究竟是怎样的含义

定义一个类 Student 并实现几个方法(在m文件中实现,h中可以不定义)

- (NSString *)mehtodOne:(int)a
                    str:(NSString *)str {
    return @"";
}

- (NSArray *)mehtodTwo:(NSArray *)a
                   str:(NSString *)str
                   count:(NSInteger)count {
    return [NSArray new];
}

-(void)readBook {
}

+ (NSInteger)methodForClass:(NSInteger)a
                     time:(long)time {
    return 1;
}

我们来打印这些方法的信息

#import 

Student *obj = [Student new];

- (void)methodInfo:(id)obj
{
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList([obj class], &methodCount);
    
    for (NSInteger i = 0; i < methodCount; i ++) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        NSLog(@"方法名:%@", NSStringFromSelector(methodName));
        
        // 获取方法的参数类型
        unsigned int argumentsCount = method_getNumberOfArguments(method);
        char argName[512] = {};
        for (unsigned int j = 0; j < argumentsCount; ++j) {
          method_getArgumentType(method, j, argName, 512);
          
          NSLog(@"第%u个参数类型为:%s", j, argName);
        }
        
        char returnType[512] = {};
        method_getReturnType(method, returnType, 512);
        NSLog(@"返回值类型:%s", returnType);
        
        // type encoding
        NSLog(@"TypeEncoding: %s", method_getTypeEncoding(method));
    }
}

打印结果为:

方法名:mehtodOne:str:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:i
第3个参数类型为:@
返回值类型:@
TypeEncoding: @28@0:8i16@20

方法名:mehtodTwo:str:count:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:@
第3个参数类型为:@
第4个参数类型为:q
返回值类型:@
TypeEncoding: @40@0:8@16@24q32

方法名:readBook
第0个参数类型为:@
第1个参数类型为::
返回值类型:v
TypeEncoding: v16@0:8

问题

问题1、方法的参数个数和打印的参数个数不一致?
问题2、类方法去哪了?

关于问题1:
这里打印的参数并不是我们定义的方法参数 而是底层objc_msgSend的参数
在底层调用objc_msgSend的时候,是有2个固定参数(id)self ---> 方法的调用者 和 SEL _cmd ---->调用的方法名,后面跟的是其他的参数信息

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

比如方法1: 在编译的时候会被转换为

NSString  *returnValue = ((NSString * (*)
                               (id,
                                SEL,
                                NSString *,
                                NSNumber *))
                objc_msgSend)
    (obj,
    @selector(mehtodOne:str:),
    a
    @"");

方法二会被编译为

NSArray  *returnValue = ((NSArray * (*)
                               (id,
                                SEL,
                                NSArray *,
                                NSString *,
                                NSInteger))
                objc_msgSend)
    (obj,
    @selector(mehtodTwo:str:count:),
    @[]
    @"",
    a);

方法三会被编译为

((void (*)(id, SEL))objc_msgSend)(obj, @selector(readBook));

这样就解释了参数的数量。

  • 方法编码的含义:
    用方法一@28@0:8i16@20举例
    第一个@表示的是返回值的标识

NSString->@
void -> v
int -> i

28表示的是 所有参数的总长度
@ 表示第一个参数的 类型 ,这里就是 self的类型
0 表示从第0位开始
:表示第二位参数的类型,这里是 SEL
8 表示从第8位开始,因为前面的一个参数是self(占位8)
i 表示第三位参数的类型 这里是 int
16 表示从第16位开始 ,因为前面有2个参数self(占位8) SEL(占位8)
@ 表示第四个参数的 类型 ,这里就是 NSString *的类型
20 表示从第20位开始 ,因为前面有3个参数self(占位8) SEL(占位8)int (占位4)
最后一个参数类型为NSString( 占位8) 所以最前面总长度为28
具体什么类型占用多少字节 可参照这篇文章 的 【补充 sieof()知识】这部分知识点

想要知道每个类型的占位符 可以通过以下方法打印
(官方也有相关文档介绍)

// 调用  
- (void)viewdidLoad{
    logTypes()
}

void logTypes(){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}


//打印结果
char --> c                  int --> i
short --> s                 long --> q
long long --> q             unsigned char --> C
unsigned int --> I          unsigned short --> S
unsigned long --> Q         float --> f
bool --> B                  void --> v
char * --> *                id --> @
Class --> #                 SEL --> :
int[] --> [3i]              struct --> {person=*i}
union --> (union_type=*i)   int[] --> ^i

官方文档搜索路径 comm+shift+0 ->


搜索方式

问题2: 类方法在哪呢?

上面打印出来的方法信息只有实例方法,那么类方法去哪里了呢?
注意上面获取方法列表的使用
Method *methodList = class_copyMethodList([obj class], &methodCount);
这样一句代码,这里是获取obj的类对象里面的方法
那么如果需要获取类方法,这里获取方法信息的对象就应该是元类对象

Class class = [obj class];
Class metaClass = object_getClass(class);
Method *methodList = class_copyMethodList([obj class], &methodCount);

方法名:methodForClass:time:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:q
第3个参数类型为:q
返回值类型:q
TypeEncoding: q32@0:8q16q24

验证方法存储位置

定义一个类 : JEObject
JEObject声明并实现2个方法
- (void)je_instanceMethod;
+ (void)je_classMethod;

这里介绍三个API

Method class_getInstanceMethod(Class cls, SEL sel)
Method class_getClassMethod(Class cls, SEL sel)
IMP class_getMethodImplementation(Class cls, SEL sel)


  • class_getInstanceMethod(Class cls, SEL sel) :从cls(类对象/元类对象)获取实例方法sel
    执行代码:
Method method1 = class_getInstanceMethod([JEObject class], @selector(je_instanceMethod));
Method method2 = class_getInstanceMethod(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));

Method method3 = class_getInstanceMethod([JEObject class], @selector(je_classMethod));
Method method4 = class_getInstanceMethod(objc_getMetaClass("JEObject"), @selector(je_classMethod));
NSLog(@"method1 = %p \n\
          method2 - %p\n\
          method3 - %p\n\
          method4 - %p",method1,method2,method3,method4);

打印结果:

method1 = 0x10f47e1b8 
method2 - 0x0
method3 - 0x0
method4 - 0x10f47e150

从打印结果来看:

method1和method4 是有值的,2、3为nil,也就是说:从类对象中能拿到实例方法,从元类中可以拿到类方法,换句话就是:实例方法在对象中,而类方法 在元类对象中


  • class_getClassMethod(Class cls, SEL sel) 从cls(类对象/元类对象)获取类方法sel
    执行代码:
    Method method1 = class_getClassMethod([JEObject class], @selector(je_instanceMethod));
    Method method2 = class_getClassMethod(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));
    
    Method method3 = class_getClassMethod([JEObject class], @selector(je_classMethod));
    Method method4 = class_getClassMethod(objc_getMetaClass("JEObject"), @selector(je_classMethod));
    NSLog(@"method1 = %p \n\
    method2 - %p\n\
    method3 - %p\n\
    method4 - %p",method1,method2,method3,method4);

打印结果:

method1 = 0x0 
method2 - 0x0
method3 - 0x10e054178
method4 - 0x10e054178

⚠️:这里就很奇怪了,method4不仅有值,而且和method3是一样的,为什么呢?
不懂就看源码

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

首先先要明白:类方法是存储在元类的方法列表中,这里传入的cls 如果是[JEObject class] 只是一个类,而不是元类 那么就会自动去找到其元类,并在其元类中找相应的方法如果传入的是元类objc_getMetaClass("JEObject"),那么就直接在其自身的方法列表中去找 method3和method4虽然写法不一样,但是进入源码中一看,其实意思是一样的,所以最后的打印结果是一样的。


  • class_getMethodImplementation(Class cls, SEL sel)
    执行代码:
IMP imp1 = class_getMethodImplementation([JEObject class], @selector(je_instanceMethod));
IMP imp2 = class_getMethodImplementation([JEObject class], @selector(je_classMethod));
IMP imp3 = class_getMethodImplementation(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));
IMP imp4 = class_getMethodImplementation(objc_getMetaClass("JEObject"), @selector(je_classMethod));
NSLog(@"imp1 = %p \n\
    imp2 - %p\n\
    imp3 - %p\n\
    imp4 - %p",imp1,imp2,imp3,imp4);

打印结果:

imp1 = 0x106763a40 
imp2 - 0x7fff503b2400
imp3 - 0x7fff503b2400
imp4 - 0x106763a70

⚠️:这里也很奇怪,imp2和imp3不仅有值,而且还是一样的,为什么呢?
不懂还是看源码

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
    if (!cls  ||  !sel) return nil;
    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

一看就明白了,如果没有最后返回的是消息转发,
在控制台上用po命令分别打印imp1、imp2、imp3、imp4得到的结果是:

(lldb) po imp1
(`-[JEObject je_instanceMethod] at JEObject.m:16)

(lldb) po imp2
(libobjc.A.dylib`_objc_msgForward)

(lldb) po imp3
(libobjc.A.dylib`_objc_msgForward)

(lldb) po imp4
(`+[JEObject je_classMethod] at JEObject.m:19)

方法的调用流程

实例方法调用过程

那么方法的调用的具体流程是什么样的呢?
iOS objc_msgSend消息发送机制

神经病院Objective-C Runtime住院第二天——消息发送与转发 - 详细的源码分析

比如执行代码:

Student *obj = [Student new];
[obj readBook];
  • 编译过程中

首先会转换成objc_msgSend函数

Student *obj = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("readBook"));

转换命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 类名.m -o out.cpp

  • 执行过程中、

1、判断obj是否为nil,如果为nil,什么都不会发生
2.在对象的<缓存方法列表>【也就是类对象的缓存中】 中去找要调用的方法,找到直接调用
3.对象的<缓存方法列表> 里没找到, 就去<的缓存列表>去找,如果没有你缓存,就去方法列表>里找,找到了就调用并缓存。
4.还没找到,说明这个类自己没有了,就会通过isa去向其父类里执行2、3。
5.当父类指向null的时候还没找到【对象的父类->父类的父类-> ..... -> NSObjec -> nil】,那么就是没有了,就进行动态解析
6.如果没有进行动态解析,那么就会报错崩溃。

上面说了,方法找到了之后的操作是 将其缓存起来并调用,,如果直接在类对象中找到了方法,我们知道 是直接缓存在类对象的缓存信息中。那么如果是在superClass中找到方法。缓存是存在那个位置呢?在源码中能找到答案:
objc_msgSend -> _class_lookupMethodAndLoadCache3 -> lookUpImpOrForward 中 在类的方法列表中找到时有这样一段代码:

Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }

在父类中找到方法的代码:

if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this 
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}

注意log_and_fill_cache这个方法和解释备注,
如果在父类中找到方法,就在这个类中进行缓存
也就是说 方法缓存的位置是在类对象的缓存信息中,而不是在父类中找到的就缓存在父类

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
/**
JEStudent:JEPerson

cls:          JEStudent
imp:          (KCObjcTest`-[JEPerson superClass])
sel:          (SEL) sel = "superClass"
receiver:     (JEStudent *) receiver = 0x0000000101437030
implementer:  JEPerson
*/
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);
}

补充:
类的方法列表位置:
class_rw_t *data() —>
class_ro_t *ro —>
baseMethodList

类方法的调用过程

类方法的调用过程和实例方法步骤大致一致,只是找方法的地方不一样

1.在的<缓存方法列表>【也就是元类的缓存中】 中去找要调用的方法,找到直接调用
2.的<缓存方法列表> 里没找到,就去<类的元类方法列表>里找,找到了就调用并缓存。
3.还没找到,说明这个类自己没有了,就会通过isa去向其元类的父类里执行1、2。
4.直到最后的父类指向null的时候【元类->元类的父类(和父类的元类是一个东西)->根源类,根源类的父类->NSobjec->nil】还没找到,那么就是没有了,就进行动态解析
5.如果没有进行动态解析,那么就会报错崩溃。


来看下面这个打印

NSObject添加一个拓展类

@interface NSObject (JE)
+ (void)je_method;
- (void)je_method;
@end

实现实例方法(⚠️类方法并没有实现,而且实例方法和类方法的方法名是一致的)

- (void)je_method
{
    NSLog(@"实例方法");
}

执行以下代码:

[NSObject je_methoded];
    NSObject *obj = [NSObject new];
    [obj je_method];

先考虑这样能否正确执行,如果能,打印结果是什么,为什么?

打印结果

实例方法
实例方法

为什么能正确打印,而且执行的都是实例方法? 以为执行类方法的时候,在其元类中没找到其方法就去去元类的父类中去找,而对于NSObject的元类的父类是其本身,所以最后有执行了其实例方法

2020.09.18 补充

在Class结构中 的data() 信息class_rw_t *data()
在它的结构中,rw结构中有 method等,rw的ro里面 也有method等。他们之间有什么区别?

ro里面 是只读属性,存的是编译是就产生的信息,比如类/类拓展 包含的方法、属性等。
而ro外层 rw中的 method等 是 类本身的信息 + 分类方法 + 通过runtime 动态添加的方法
类似于:
rw中的method = ro中的basemethod + 分类的方法 + class_addMethod 。
不管是编译的产生的方法 还是动态运行时 新增的方法 ,都会存在于 rw的methods中。

你可能感兴趣的:(iOS 底层--Class探索和方法执行过程)