笔记-如何优雅姿势探究类结构(类的底层原理解析)

1,045

类的底层原理

实例对象、类对象、元类之间的关系

直接上代码,看结果之后解释一下

思考一下这几个问题:类对象class1class2class3打印的地址分别是什么情况?
为什么class4是元类,class5是根元类?

打印结果

可以看出:
类对象class1class2class3的地址是同一个,因为一个对象的类对象只有一个。 object_getClass获取对象的类,类对象存储的位置是哪里?在文章笔记-runtime源码解析之让你彻底了解底层源码里讲述过,它是存在元类中,所以class4为元类,同样class5为根元类。如果还有疑问的话,可以接着往后看,或者评论里留言给笔者。 下面进行一些lldb调试,直接上图

  • person->isa输出了person对象的isa的指向,是ZBPerson这个类,地址是0x6000034232c0,调试的输出的结果和打印输出的结果一致
  • 用命令x 0x6000034232c0输出的是ZBPerson这个类内存的情况,图上说明了,前八字节指的是isa,为什么呢?看下面几段源码

Classobjc_class类型的

上面一段源码里可以看出,类的内部结构,前8字节为隐藏属性isa,接着后8字节是superclass, 接着是16字节的cache等等,具体的后面分析。

  • 上面lldb调试过程中也说类,isa为优化过的,每次打印输出的时候,都&上了一个值0x00007ffffffffff8,这又是从哪里得出来的结论呢?请看下面源码:

嗯,讲述到这里,上面的lldb调试的过程,相信你是可以明白的,其实最终还是回到文章笔记-runtime源码解析之让你彻底了解底层源码里的一幅图

类结构

直接上源码

上面源码,我只截图了部分代码,下面其实还有很长,这里不做说明。 isa、superclass、cache在上面简单描述了,这里不再重复,着重看下面,直接看下面代码
这里有我们熟悉的methods、properties、protocols,往下走

这里的list_array_tt是一个二级指针型的,存放着我们常用的属性列表、方法列表、协议列表。 上面的截图,我也只截图了一部分,里面有个标红的protected,其实还有着privatepublic。 这又与我们日常的开发的公有属性、私有属性等等相呼应。

除了methods、properties、protocols,还有一个class_ro_t需要看一下,上源码

这里有到面试题,看下面代码:

    Class ZBClass = objc_allocateClassPair([NSObject class], "ZBClass", 0);
    // 添加方法--属性
    NSString *name = @"name";
    
    objc_registerClassPair(ZBClass);
    class_addIvar(ZBClass, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id));
    
    id zbClass = [ZBClass alloc];
    [zbClass setValue:@"Zuobian" forKey:@"name"];
    NSLog(@"name == %@", [zbClass valueForKey:@"name"]);

上面这段代码,能否正常运行?

报错指出没有这个key,但是上面代码中确实已经添加,那么只是说明添加失败了,为什么呢?

如果把这两句话颠倒一下,打印查看结果

    class_addIvar(ZBClass, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id));
    objc_registerClassPair(ZBClass);

说明在注册这个函数之前添加Ivar是成功的,回到class_ro_t源码可以看出
ivar_list_tconst。一旦这个类创建完毕,就不能进行修改.

MachOView查看类结构

先编译运行项目,然后找到可执行文件

把这个可执行文件拖到MachOView里,下图显示

然后打印出当前类的地址,通过image list找到首地址,通过计算器算出偏移量

得到结果0x3FE0,然后到MachOView里查找

通过lldb调试查看类结构

编译运行下面代码

得到里类对象以及元类对象的地址。

通过源码可以知道,我们想要得到class_rw_t *data(),就需要知道class_data_bits_t bits,而要知道class_data_bits_t bits,我们就需要通过类对象的地址进行指针偏移来获得。8字节+8字节+16字节--->移2位便能获取到class_data_bits_t bits
找到class_rw_t *data()后,打印出来
对比下面的源码看一下
对比之后,是不是我们想要的东西都在里面class_ro_t、methods、properties、protocols

再进入class_ro_t细看一下

输出的结果很明确里,当打印baseMethodList时,还同时给出里方法名、方法签名、所在的类以及多少行;有兴趣的读者还可以通过这种方式打印出类里的其他内容。

这一切看上去似乎很完美,给大家看一下ZBPerson.m文件里的内容

那么问题就来了,上面的lldb打印只打印出了instanceMethod方法,那其他两个方法都去哪里了呢?

nice~类方法存储在元类中,上面调试的都是类对象的结构,下面的就是类方法的调试