什么是类
Class是一个指向结构体的指针。
typedef struct objc_class *Class;
此时结构体objc_class又是什么呢?
答:objc_class是继承于objc_object的结构体,或许是这个原因,类也称之为对象。
它包含父类的objc_class指针、cache、bits。由于objc_class与objc_object是继承关系,因此,在objc_class也包含了isa指针。
struct objc_class : objc_object {
...
// Class ISA;
// isa_t isa; 继承自objc_object结构体
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
struct objc_object {
private:
isa_t isa;
};
验证
(lldb) p/x cls
(Class) $1 = 0x0000000100008438 HQPerson
(lldb) x/8g 0x0000000100008438
0x100008438: 0x0000000100008410 0x0000000100357140
0x100008448: 0x000000010034f390 0x0000000000000000
0x100008458: 0x0000000100008080 0x00000001000ac928
0x100008468: 0x0000000000000000 0x0000000000000000
//0x0000000100008410:isa指针
//0x0000000100357140:superclass指针
//cache
//0x000000010034f390: buckets
//0x0000000000000000: cache中的union
//0x0000000100008080:bits
(lldb) po 0x0000000100008410
HQPerson //此时是HQPerson的元类
(lldb) po 0x0000000100357140
NSObject //此时是HQPerson的父类
isa指向
先给出结论:
- 对像的isa指向类
- 类的isa指向的是元类
- 元类的isa指向根元类(NSObject)
- 根元类的isa指向自己。
验证:
//获取对象的地址,并读取地址中的内存
(lldb) p/x person
(HQPerson *) $35 = 0x00000001006830e0
(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000
//解析对象中的isa
(lldb) p/x 0x011d80010000843d & 0x00007ffffffffff8
(long) $39 = 0x0000000100008438
//0x0000000100008438:对象的isa指向类
//读取类的isa
(lldb) x/4g 0x0000000100008438
0x100008438: 0x0000000100008410 0x0000000100357140
0x100008448: 0x0000000101005f10 0x0001803c00000003
//0x0000000100008410:指向元类
//读取元类的isa
(lldb) x/4g 0x0000000100008410
0x100008410: 0x00000001003570f0 0x00000001003570f0
0x100008420: 0x000000010034f390 0x0000e03500000000
//0x00000001003570f0:指向根元类
(lldb) po 0x00000001003570f0
NSObject
//读取根元类NSObject的isa
(lldb) x/4g 0x00000001003570f0
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x00000001007772a0 0x0004e03100000007
//0x00000001003570f0:根元类的isa指向自己
superclass
先给出结论:
- 类的superclass是父类;
- 父类的superclass是 NSObject;
- NSObject 的superclass 是nil;
- 元类的superclass 是父元类;
- 父元类的superclass 是根元类;
- 根元类的superclass 是NSObject;
验证:
//读取对象的地址
(lldb) p/x person
(HQPerson *) $46 = 0x00000001006830e0
//获取对象的superclass
(lldb) p/x person.superclass
(Class) $47 = 0x0000000100357140 NSObject
//对象的superclass 是NSObject。这是因为该对象是直接继承于NSObject
//获取父类的superclass
(lldb) p/x ((NSObject*)0x0000000100357140).superclass
(Class) $48 = nil
//NSObject的superclass是nil
//根据前面知识读取元类的isa
(lldb) x/4g 0x0000000100008410
0x100008410: 0x00000001003570f0 0x00000001003570f0
0x100008420: 0x000000010034f390 0x0000e03500000000
//0x00000001003570f0:此处为元类的superclass,从地址上可以得出,0x00000001003570f0为根元类地址。
//根据前面知识读取根元类的内存时,可以根元类的superclass
(lldb) x/4g 0x00000001003570f0
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x00000001007772a0 0x0004e03100000007
//0x0000000100357140:此处为根元类的superclass,从地址上可以得出,0x0000000100357140为NSObject的地址。
isa与superclass总结图

如何alloc一个对象
主要是分为三步:
- 根据16字节对齐原理,确定当前类的大小size。
- 根据size的大小开辟足够的内存空间。
- 将对象与类通过isa进行关联。
类的大小
类多大呢?从类的结构体中,可以很清楚的得到类的大小。
类的大小 = 16字节对齐(isa的大小 + superclass的大小 + cache的大小 + bits的大小)。
- isa的大小:64位域,占8字节。
- superclass的大小:指针类型,占8字节。
- cache的大小:cache_t结构体大小 = _bucketsAndMaybeMask指针大小(8字节)+ 联合体大小(8字节) = 16字节
- bits的大小:8字节。
- isa的大小 + superclass的大小 + cache的大小 + bits的大小 = 8 + 8 + 16 + 8 = 40,由于需要16字节对齐,因此,类的大小为48字节。
开辟空间
已经知道类对象的大小,此时调用calloc函数,向内存中申请大小为48(instanceSize计算的大小)的内存。
obj = (id)calloc(1, size);
验证:
//调用calloc之前,通过p查看obj的内容
(lldb) p obj
nil
//调用calloc之后,再通过p查看obj的内容
(lldb) p obj
(id) $16 = 0x00000001006830e0
//calloc之后,得到一个内存地址,并且将该地址指向的内存内容清空
(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x0000000000000000 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000
将类与对象通过isa进行关联
在第二步中,开辟了一段内存空间,并将内存中的内容清空,此时需要将该内存与isa进行关联了。那如何进行关联呢?
obj->initInstanceIsa(cls, hasCxxDtor);
initInstanceIsa 函数内容
new一个isa_t结构体newisa
isa_t newisa(0);
(lldb) p/x newisa
(isa_t) $18 = {
bits = 0x0000000000000000
cls = nil
= {
nonpointer = 0x0000000000000000
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000000
shiftcls = 0x0000000000000000
magic = 0x0000000000000000
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000000
}
}
将newisa的bits初始化为ISA_MAGIC_VALUE(0x001d800000000001ULL)
//给newisa的bits赋初值
newisa.bits = ISA_MAGIC_VALUE;
(lldb) p/x newisa
(isa_t) $19 = {
bits = 0x001d800000000001
cls = 0x001d800000000001
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000000
shiftcls = 0x0000000000000000
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000000
}
}
给isa的has_cxx_dtor进行赋值
//给isa的has_cxx_dtor进行赋值
newisa.has_cxx_dtor = hasCxxDtor;
(lldb) p/x newisa
(isa_t) $22 = {
bits = 0x001d800000000005
cls = 0x001d800000000005
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000001
shiftcls = 0x0000000000000000
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000000
}
}
设置isa的shiftcls。isa的shiftcls就是用来存储类指针的
//先得到类对象地址
(lldb) p/x newCls
(Class) $24 = 0x0000000100008438 HQPerson
//将类对象的地址向右移3位
(lldb) p/x 0x0000000100008438>>3
(long) $26 = 0x0000000020001087
//将得到的地址存入shiftcls
shiftcls = (uintptr_t)newCls >> 3;
(lldb) p/x newisa
(isa_t) $27 = {
bits = 0x001d80010000843d
cls = 0x001d80010000843d HQPerson
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000001
shiftcls = 0x0000000020001087
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000000
}
}
设置isa的extra_rc为1,即将对象的引用计数加1
(isa_t) $28 = {
bits = 0x011d80010000843d
cls = 0x011d80010000843d HQPerson
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000001
shiftcls = 0x0000000020001087
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000001
}
}
此时可以看到,已经将obj
与类
进行了关联。
(lldb) p/x obj
(HQPerson *) $30 = 0x00000001006830e0
(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000
isa位域
前面一直描述,如何通过isa将类与对象进行关联,那isa是什么呢?
isa是一个64位域的联合体。
union isa_t {
uintptr_t bits;
}
我们以x86_64为示例,来看一下64位域中到底存储了什么?
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
-
nonpointer
:表示是否对isa 指针
开启指针优化- 0:纯isa指针。
- 1:不止是类对象地址, isa 中包含了类信息、对象的引用计数等。
-
has_assoc
:关联对象标志位,0没有,1存在。 -
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器.- 如果有析构函数,则需要做析构逻辑。
- 如果没有,则可以更快的释放对象。
-
shiftcls
:存储类指针的值
。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。x86_64 架构 有 44位。 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间。 -
weakly_referenced
:志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。 -
deallocating
:标志对象是否正在释放内存。 -
has_sidetable_rc
:当对象引用技术大于 10 时,则需要借用该变量存储进位。 -
extra_rc
:当表示该对象的引用计数值,实际上是引用计数值减 1,例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10,则需要使用到下面的 has_sidetable_rc。
通过一个示例,直观的看一下对象的isa位域:
//HQPerson* person = [HQPerson alloc];
(lldb) p/x person
(HQPerson *) $35 = 0x00000001006830e0
//0x00000001006830e0:指向objc_object结构体的指针
(lldb) x/8g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000
//0x011d80010000843d:表示isa联合体
(lldb) p/x (isa_t)0x011d80010000843d
(isa_t) $50 = {
bits = 0x011d80010000843d
cls = 0x011d80010000843d HQPerson
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000001
shiftcls = 0x0000000020001087
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
unused = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000001
}
}
总结:
-
万物皆对象。
- 对象就是一个指向objc_object结构体的指针;
- 类就是一个指向objc_class结构体的指针,且objc_class继承于objc_object。
isa与superclass的走位图。
-
对象与类
之间是通过isa
进行关联。 - 对类进行alloc操作,重要三步骤:
计算类的大小size。
根据size大小开辟内存空间。
将对象与类通过isa进行关联。
网友评论