OC对象底层探索 — isa的初始化和指向分析
2020-03-08 本文已影响0人
Dezi
用于记录iOS底层学习,以备后续回顾
OC对象底层探索
前言
通过之前的探索,我们了解了对象是怎么创建的,对象的内存是如何分配的。而探索OC对象就避免不了要了解isa
,下面我们结合源码对isa进行详细的分析。
一、isa的初始化及其本质
1.1 isa的初始化
对象初始化的alloc方法内部,会调用initIsa
方法。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
对象通过
isa
关联类。
nonpointer = true
做初始化的赋值;!nonpointer
直接赋值cls。
1.2 isa的本质
a. isa是什么
isa
:实际是一个isa_t
结构,看源码可知,isa是一个联合体,在类中以Class 对象存在,用来指向类的地址,大小为 8 个字节,也就是 64 位。
联合体的特性
:内存共用,或者说带有互斥
特性,意思就是赋值了cls
,就不对其他成员赋值了。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
// 指向、绑定 类
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
// 位域
ISA_BITFIELD; // defined in isa.h
};
#endif
};
b. ISA_BITFIELD
组成及各成员是用来做什么的
我们基于arm64架构来解读ISA_BITFIELD
,一共为8字节64位:
# define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
nonpointer
表示是否对isa指针开启优化(我们现在的都是开启了优化),值为0:纯isa指针; 值为1:不止是类对象地址,isa中还包含了类信息,对象的引用计数等;has_assoc
是否有关联对象,值0无,值1有。has_cxx_dtor
是否有c++或者objc析构函数,如果有析构函数,先走析构逻辑,没有就更快的释放对象。shiftcls
存储类指针的值,开启指针优化的时候,在arm64架构中有33位存储类指针。magic
用来调试器判断当前对象是真的对象还是没有初始化的空间。weakly_referenced
标志对象是否指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快的释放。deallocating
标志对象是否正在释放。has_sidetable_rc
当对象的引用计数大于10的时候,会借用该变量存储进位。extra_rc
表示该对象的引用计数的值,实际上是引用计数的值减去1。例如:该对象的引用计数为10,则该变量的值为9,若超过10,则需要用到has_sidetable_rc
。
二、isa指向分析
内存里面,对象的属性位置是会发生变化的,但对象的第一个属性必然是isa。
代码段:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "DZPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
DZPerson *object = [DZPerson alloc];
object_getClass(object);
NSLog(@"Hello! %@",object);
}
return 0;
}
2.1、isa关联对象和类
使用object_getClass(object)
方法分析对象是如何关联类的:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
# define ISA_MASK 0x00007ffffffffff8ULL
由以上源码可知:对象通过(Class)(isa.bits & ISA_MASK)
方法得到类地址。
我们来亲自验证:
根据上述结果,我们可以确定对象是通过isa关联类的。
2.2、isa指向
lldb命令
x/4gx objc
打印objc的4段内存信息。扩展:x/6gx就是打印6段内存信息。p/t
打印二进制信息,p/o
打印八进制信息,p/x
打印十六进制信息。
我们通过(Class)(isa.bits & ISA_MASK)
方法,使用lldb一级一级的调试获取指向信息:
由上图分析可得:
- 实例对象的isa指向类
- 类的isa指向元类
- 元类的isa指向根元类
- 根元类的isa指向自己
下面我们再来看看官方给出的isa指向图:
官方isa指向图虚线代表了
isa
的指向:实例对象 -> 类 -> 元类 -> 根元类 -> 根元类本身。需要注意的是只有根元类会指向自身类。
实线代表了继承关系:DZTeacher -> DZPerson -> NSObject -> nil。这里需要注意的是根元类的父类是NSObject,NSObject的父类是nil。