objc_object & objc_class的区别
objc_object
对应对象
,是对象
在底层的的数据结构
。objc_class
对应类
,是类
在底层的数据结构
id
为objc_object指针
的别名
typedef struct objc_object *id;`
class
为objc_class指针
的别名
typedef struct objc_class *Class;
objc_class
继承于objc_object
,万物皆对象
struct objc_class : objc_object {
isa
源码分析
探究objc_object,我们知道objc_object
在内存中由isa_t isa
和成员变量
构成。
查看isa_t的源码
union isa_t {
uintptr_t bits;
private:
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
- isa是个
联合体(共用体)
- isa在新版内存优化后,通过
位域
的方式,由原来64位
单纯作为类指针变为剩余空间可以存放其它信息。例如在x86架构下,中间44位是存放类指针地址,其它为存放了关联对象信息、弱引用、散列表等信息,更大限度的节省内存空间
。 - 其中
ISA_MASK
作为掩码
,在ISA_BITFIELD位域的冗余信息
中过滤出shiftcls
,类指针的值.
define ISA_MASK 0x00007ffffffffff8ULL
define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
我们可以看到他们的流程
graph TD
对象对应objc_object --> objc_object由isa构成
objc_object由isa构成 --> isa指向类objc_Class
isa指向类objc_Class --> objc_Class继承于objc_object
objc_Class继承于objc_object --> objc_object由isa构成
打印isa内存信息
对象的isa指向类,那么类的isa指向什么?
实例中我们探究XXPerson的内存情况
XXPerson *p = [XXPerson alloc];
NSLog(@"%@",p);
p
打印出对象的内存地址
x/4gx
计算出当前对象指向的内存地址相邻4个单位的内存地址
p/x
isa地址 & ISA_MASK 计算出shiftcls(类对象)的指针地址
po
打印出类对象描述信息
- 根据
步骤3
计算出的类对象地址,重复步骤2-4
- p->isa == p.class ==
0x0000000100008270
== XXPerson - p.class->isa == p.class.class ==
0x0000000100008248
== XXPerson - (p.class->isa)->isa == p.class.class.class ==
0x00007fff88c65ca0
== NSObject - ((p.class->isa)->isa)->isa == p.class.class.class.class ==
0x00007fff88c65ca0
== NSObject
元类(metal class)
类的isa指向的是元类
我们可以看到p.class 和 p.class.class的打印内存地址不同
,但通过po
打印出来的描述信息相同
,都为XXPerson
。
打开MacOView,在符号表中搜索XXPerson,找到了_OBJC_METACLASS_$_XXPerson
, 这里的类型就对应了p.class->isa所指向的类型,即元类
根元类 (root metal class)
元类的isa指向的是根元类
根元类
打印出来的描述信息为NSObject
,且再往后isa一直指向0x00007fff88c65ca0
这个地址,po打印的描述信息为NSObject
。
根元类
与NSObject的元类
指向的地址0x00007fff88c65ca0
是一致
的根元类
与NSObject类
指向的地址0x00007fff88c65cc8
不同根元类isa
指向的还是根元类
总结
非NSObject
实例的isa
-->类
类的isa
-->元类
元类的isa
-->根元类
根元类的isa
-->自己
NSObjectNSObject
-->NSObject的类
NSObject的类
-->NSObject的元类
NSObject的元类
-->自己
NSObject的元类
即根元类
superclass
元类存在继承关系
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
打印结果
LGPerson是LGTeacher的父类,从上述打印结果可以看到,元类存在继承关系
,元类的父类是父类的元类。
NSObject的父类是nil
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
打印结果
NSObject元类的父类是NSObject类
上代码
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
打印结果
这里和上面打印出来的NSObject类
的地址相同,从另一的角度理解,元类也是类
,所以他们的继承关系图中,最终指向的还是NSObject类
,万物皆NSObject
。
类、元类继承关系的图解
苹果官方文档isa走位图和类,元类的继承图
class_data_bits_t bits
首先,我们查看元类的objc_class代码
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_data_bits_t bits地址的定位
方法,成员变量,协议之类的信息存放在bits
,我们可以通过地址偏移的方式定位类对象中的bits
其中我们确定的是类对象继承自objc_object所以对应的第一个成员变量isa
占了8个字节
,superclass
也占了8个字节
,但是不知道cache_t cache
所占的内存空间大小。查看源码,通过去掉方法,全局变量等不占对象内存空间的代码,提炼出一下部分的代码
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied;// 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
};
由于结构体的下面那那部分成员时联合体,共享内存,通过简单的计算得知, cache_t cache所占的内存8 + 8 为16个字节
,所以,加上上面的isa和superclass取class_data_bits_t bits的地址需要偏移32个字节
成员变量信息
按照如下步骤打印内存
x/4gx
LGPerson.class,得出内存首地址0x100008380
p/x 0x100008380 + 0x20
,得出偏移后的内存0x00000001000083a0
- 强转类型p
(class_data_bits_t *)0x00000001000083a0
,得出lldb中变量$97 - 通过
p $97->data()
,获取class_data_bits_t bits
中的数据,得到(class_rw_t *) $98 = 0x0000000101233ca0 - p
$98->properties()
获取类成员信息
(const property_array_t) $100 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008260
}
arrayAndFlag = 4295000672
}
}
}
- p
$100.list
- p
$101.ptr
- p
*$113
p *$113
(property_list_t) $114 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
- 打印结果是个数组,所以通过
get
方法,即p$114.get(0)
打印内存情况
(property_t) $115 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
方法
x/4gx
LGPerson.class,打印类对象内存地址
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x00000001012340f0 0x0002802800000003
- 输入p/x 0x100008380 +
0x20
获取class_data_bits_t bits的32个字节的偏移地址
$117 = 0x00000001000083a0
- 输入p (class_data_bits_t *)0x00000001000083a0,类型强转为
class_data_bits_t指针
(class_data_bits_t *) $118 = 0x00000001000083a0
- 输入p $119->
methods()
,获取方法信息
(const method_array_t) $120 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008160
}
arrayAndFlag = 4295000416
}
}
}
- 输入p $120.list
(const method_list_t_authed_ptr<method_list_t>) $121 = {
ptr = 0x0000000100008160
}
- p $121.ptr
(method_list_t *const) $122 = 0x0000000100008160
- 输入p *$122
(method_list_t) $123 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
- 输入p $123.
get(0)
.big()
,其中get(0)
为获取数组的method_t的第一个元素,big()
打印方法信息
(method_t::big) $125 = {
name = "sayNB"
types = 0x0000000100003f77 "v16@0:8"
imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}