如何打印类信息
通过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:在
特殊
情况下,如果是父类
,实例对象
指向当前的类
,类
指向当前的元类
,元类
指向根元类
,根元类
还是指向根元类
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
个字节 - 通过
首值
进行内存偏移
取值
探索类对象
前面我们已经知道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++底层会默认添加