如何打印类信息
通过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,得出类的指针地址

算法特殊性

如图可以看出0x00000001000021e0和0x00000001000021b8,打印的值都是LGPerson,即使0x00000001000021b8 & 0x00007ffffffffff8ULL得出的指针地址还是一样的,所以打印的结果还是LGPerson,其实这里就是算法的特殊性(三位抹零),里面做了处理。
那么为什么0x00000001000021e0和0x00000001000021b8打印的结果都是LGPerson呢?
其实这个东西就是元类,它没有非常清晰的概念。它不能向类一样时刻查看。它是系统给予的非常重要的东西。
- 当前
对象isa归属于类 - 类是一个
对象,它的归属是元类 - 所有类方法的存储都存在
元类里面
元类的创建和定义都是由编译器自动完成
NSObject的根元类
通过lldb命令不停爬,最终从元类来到NSObject。顺序就是isa 对象-> 类(LGPerson)->元类(LGPerson)-> NSObject(根元类)

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

由此我们考虑一个问题(面试题目)
请问类对象和类信息在内存里面存在几份?
其实类对象内存里面永远只存在一份
但是上图可以看出有两个NSObject,这又是为什么呢?
其实NSObject是根元类和LGPerson一个原理isa 对象-> 类(LGPerson)->元类(LGPerson)-> NSObject(根元类)
那就我们来验证一下NSObject到底有多少份

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

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

由此可以总结出一幅图

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

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

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

- 很多的类底层都来自于
objc_class结构体,因此NSObject也是来自于objc_class -
objc_object是我们当前的根对象,是一个结构体
不管是NSObject还是对象都有一个isa,isa来自于objc_object,同时objc_class继承于objc_object所以也是有isa。

面试问题
objc_object和对象的关系?
所有的对象都是以objc_object为模版继承过来的
总结:所有的对象+类+元类 都有isa. 所有的对象来自于NSObject(OC), 真正到底层的话是objc_object(C ,C++),struct objc_object *class定义一个class类型(所有的class都是以objc_object为模版)
类的内存分布

如图可以看到//Class ISA的位置,这只是提示作用,而不是单纯的屏蔽isa,但是我们在打印中可以发现LGPerson是有isa(0x00000001000021b8),原因是objc_class继承于objc_object,因此也拥有了父类的isa。
类结构源码
下图可以看出10同时赋值给了a和b,其实就是一种copy即值拷贝,但是a和b的地址是不同的。


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


内存偏移
有时我们取不到值的时候而是取到对象,现在我们可可以拿到首值通过偏移的方式获取值。
- 因为栈是连续的,相差
4个字节 - 通过
首值进行内存偏移取值
内存偏移
image
探索类对象
前面我们已经知道objc_class继承于objc_object但是我们怎么知道superclass和cache占用多少字节呢?达到我们平移到bits的位置。
那么我们就需要知道isa+superclass+cache = bits

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

-
isa是占用8字节 -
superclass也是class类型,所以也是占用8字节 -
cache_t占用16字节
通过lldb命令,根据首地址并且平移32字节打印bits信息

$1因为是指针类型,所以调用data()方法。同时这是781的最新改版,旧的版本有显示methdos properties等数据。
- 指针和对象使用
-> - 结构体使用
.符号
781源码如何打印methdos properties等信息

- 在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)打印的是属性的set和get方法,由系统自动生成。p $8.get(2)oc封装于c++底层会默认添加


