IOS- 底层原理-类结构分析

如何打印类信息

通过lldb指令打印类信息

  • 通过isa指针地址 & ISA_MASK
    *NSObject提供的class API
  • runtime底层调用API
通过isa指针地址 & ISA_MASK

通过alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone->initInstanceIsa->initIsa->isa_t找到位域位置(目前是处于macOS,所以使用的是x86_64)

  • 移动端 :__arm64__
    • ISA_MASK:0x0000000ffffffff8ULL
  • mac OS __x86_64__
    • ISA_MASK:0x00007ffffffffff8ULL

输入x/4gx person1,打印对象的isa指针得到0x001d8001000021e5,通过0x001d8001000021e5 & 0x00007ffffffffff8ULL得到类的指针地址``0x00000001000021e0

指针地址

NSObject提供的class API

通过lldb命令输入p/x LGPerson.class,调用NSObject提供的class API,得出类的指针地址

指针地址

runtime底层调用的API

通过lldb命令输入p/x object_getClass(person1),调用runtime底层调用API,得出类的指针地址

指针地址

算法特殊性

算法特殊性

如图可以看出0x00000001000021e00x00000001000021b8,打印的值都是LGPerson,即使0x00000001000021b8 & 0x00007ffffffffff8ULL得出的指针地址还是一样的,所以打印的结果还是LGPerson,其实这里就是算法的特殊性(三位抹零),里面做了处理。

那么为什么0x00000001000021e00x00000001000021b8打印的结果都是LGPerson呢?
其实这个东西就是元类,它没有非常清晰的概念。它不能向类一样时刻查看。它是系统给予非常重要的东西。

  • 当前对象isa归属于类
  • 类是一个对象,它的归属是元类
  • 所有类方法的存储都存在元类里面

元类的创建和定义都是由编译器自动完成

NSObject的根元类

通过lldb命令不停爬,最终从元类来到NSObject。顺序就是isa 对象-> 类(LGPerson)->元类(LGPerson)-> NSObject(根元类)

NSObject的根元类

进行验证NSObject是内存的NSObject吗?
通过lldb输入p/x NSObject.class得到结果0x00000001003f0140可以看出和0x00000001003f00f0是不同的。

NSObject的根元类

由此我们考虑一个问题(面试题目)
请问类对象和类信息在内存里面存在几份?
其实类对象内存里面永远只存在一份

但是上图可以看出有两个NSObject,这又是为什么呢?
其实NSObject是根元类和LGPerson一个原理isa 对象-> 类(LGPerson)->元类(LGPerson)-> NSObject(根元类)

那就我们来验证一下NSObject到底有多少份

通过上面发现地址都一样可以验证类对象内存永远只存在一份

其实0x00000001003f0140打印出的NSObject是来自于NSObject的元类(根元类)
我们可以通过lldb命令继续验证,原理就是类的对象指向元类,如图可以发现打印结果是一样就此就验证了上面的说法。

NSObject元类

于此同时,我们在NSObject根元类的基础上继续x/4gx打印信息,并且&0x00007ffffffffff8ULL,可以发现它还是指向自己

NSObject根元类指向自己

由此可以总结出一幅图


非常著名的图
  • 1:实例对象的isa指向class(类),类的isa指向元类,元类的isa指向根元类
  • 3:RootClass是来自于NSObject的实例对象和NSObject的类,NSObject的类指向NSObject根元类根元类根元类还是指向自己(少了一步元类的指向)。
  • 2:在特殊情况下,如果是父类实例对象指向当前的指向当前的元类元类指向根元类根元类还是指向根元类
    image
image

LGTercher 继承于 LGPerson是继承关系 即继承关系来自于类
但是实例对象tercher和实例对象person没有继承关系 即实例对象没有继承关系
NSObject父类的继承关系来自于nil(找到NSObject的类型就结束了)
元类也存在继承,根元类指向NSObject,万物皆是由NSObject创建即万物皆对象

上下层的对接

搜索objc_object进入objc_object源码来自于OBJC_ISA_AVAILABILITY

objc_object源码

通过搜索struct objc_class {进入objc_class源码,OBJC2_UNAVAILABLE旧的同时已经被废除

image

  • 很多的类底层都来自于objc_class结构体,因此NSObject也是来自于objc_class
  • objc_object是我们当前的根对象,是一个结构体

不管是NSObject还是对象都有一个isaisa来自于objc_object,同时objc_class继承于objc_object所以也是有isa

image

面试问题
objc_object 和对象的关系?
所有的对象都是以objc_object为模版继承过来的

总结:所有的对象+类+元类 都有isa. 所有的对象来自于NSObject(OC), 真正到底层的话是objc_object(C ,C++),struct objc_object *class定义一个class类型(所有的class都是以objc_object为模版)

类的内存分布

image

如图可以看到//Class ISA的位置,这只是提示作用,而不是单纯的屏蔽isa,但是我们在打印中可以发现LGPerson是有isa(0x00000001000021b8),原因是objc_class继承于objc_object,因此也拥有了父类的isa

类结构源码

下图可以看出10同时赋值给了ab,其实就是一种copy值拷贝,但是ab的地址是不同的。

image

image

观察下图,可以发现指针地址内存地址都不同,p1和p2都创建了新的内存地址,并且他们都是来自于LGPerson创建的,但是他们指针地址不同,其实指向它们的是第二个指针地址指针的指针

image

image.png

内存偏移

有时我们取不到值的时候而是取到对象,现在我们可可以拿到首值通过偏移的方式获取值。

  • 因为栈是连续的,相差4个字节
  • 通过首值进行内存偏移取值
    内存偏移

    image

探索类对象

前面我们已经知道objc_class继承于objc_object但是我们怎么知道superclasscache占用多少字节呢?达到我们平移到bits的位置。
那么我们就需要知道isa+superclass+cache = bits

image

cache_t是一个结构体,结构体的指针是8字节,但是占用的大小是根据内部的属性进行判断。
点击跳转cache_t源码,静态和函数不存在结构体中,所以可以排除,那么需要计算只有四个位置

image

  • isa是占用8字节
  • superclass也是class类型,所以也是占用8字节
  • cache_t占用16字节

通过lldb命令,根据首地址并且平移32字节打印bits信息

bits数据信息

$1因为是指针类型,所以调用data()方法。同时这是781的最新改版,旧的版本有显示methdos properties等数据。

  • 指针和对象使用->
  • 结构体使用.符号

781源码如何打印methdos properties等信息

bits方法信息
  • 在bits信息的基础上,通过p $5.methods()命令中的methods方法是由class_rw_t提供的,返回的实际类型是method_array_t
  • 由于list类型是method_list_t,是一个指针,所以通过p *$7获取内存中的信息,也证明了bits中存储了method_list
  • p $8.get(1)p $8.get(2)打印的是属性的setget方法,由系统自动生成。p $8.get(2)oc封装于c++底层会默认添加
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。