【iOS开发】底层探索之对象的底层结构(下)——isa、继承链
探索isa
isa是什么?接上一章节来说,OC对象的本质为结构体
,NSObject_IMPL
为 NSObject
的 结构体,每个OC实例都会包含一个继承自NSObject
的 isa
指针。但是在__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
isa是一个指向Class的指针,那么isa的本质又是什么呢?
struct objc_object {
private:
isa_t isa;
public:
省略...
}
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
};
所以isa指针也可以称为isa_t结构体:
其中 ISA_BITFIELD
是一个宏定义,在arm64的环境下具体内容如下:
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
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;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
可以看到在 isa_t 联合体中不仅仅表明了指向对象的地址信息,而且这个 64 位数据还记录了其 bits 情况以及该实例每一位保存的对象信息。
在看一张图帮我们理解一下上面的代码:
我们再来看一下bits的64位存储分布图(以arm64为例)
从代码中我们可以看见isa_t提供了两个成员变量,一个cls一个bits,cls和bits是互斥的,既只能同时给一个变量赋值。
isa关联类
上一篇博客我们讲解了alloc方法的执行流程,我们来回顾一下基本的流程:alloc
->_class_createInstanceFromZone
-> initInstanceIsa
->initIsa
,现在我们探究initIsa方法,isa是怎么和类关联起来的,首先先看一下initIsa
方法的代码:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// 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;
}
根据调试结果可以看到此时cls的值就是类,并且shiftclass中保存了类的信息,这个过程进行完之后isa就和我们的类关联起来了。
探究Class
我们在学习Class要知道下面这些知识,先放一段代码:
//instance实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
//class对象,类对象
//objectClass1~5都是NSObject的类对象
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
//元类对象(将类对象当作参数传入进去)
Class objectMetaClass = object_getClass([NSObject class]);
Class objectMetaClass2 = [[NSObject class] class];
//判断是不是元类对象
NSLog(@"instance - %p %p", object1, object2);
NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
输出情况:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
mateClass - 0x7fff91da20f0 0x7fff91da2118 1
首先我们先了解一下iOS-实例对象( instance)、类对象(class)、元类对象(meta-class)的内部结构分析
Class是什么?Class本质上是一个结构体类型:
typedef struct objc_class *Class;
实例对象(instance)
实例对象(instance)是什么?实例对象的内部结构是什么?
首先instance对象是通过类alloc init出来的对象,每次alloc init都会产生新的instance对象。
其次实例对象的内部结构为:
struct objc_object {
private:
isa_t isa;
//以下省略是其他继承添加成员变量
}
实例对象(instance)在内存中存储的信息有isa,成员变量的具体值,所以实例对象在内存中分配有不同的内存和存储空间
class对象 (类对象)
通过以下方法可以获得类对象,每个类在内存中有且只有一个class对象。
Class personClass1 = [person1 class];
Class personClass2 = [Person class];
Class personCalss3 = object_getClass(person1);
NSLog(@"---personClass1:%p",personClass1);
NSLog(@"---personCalss2:%p",personClass2);
NSLog(@"---personCalss3:%p",personCalss3);
class对象 (类对象)里面存放有isa,superclass,属性,对象方法,协议和成员变量(注意:实例对象中存放的是成员变量的具体值,在这里存放的是成员变量信息,比如名称类型等)。
meta-class对象(元类对象)
怎么创建元类对象呢,如下所示:
Person *person1 = [[Person alloc]init];
Class personClass1 = [person1 class];
// 元类对象
Class metaClass = object_getClass(personClass1);
NSLog(@"---personClass1:%p",personClass1);
NSLog(@"---metaClass:%p",metaClass);
与类对象一样,每个类在内存中只要一个meta-calss类。里面存放的有isa,superclass,类对象
关于Class的一些问题以及总结
//创建实例对象
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
NSLog(@"instance obj: %p, %p", obj1, obj2);
//创建Class类对象
Class ClassObj1 = [NSObject class];
Class ClassObj2 = [obj1 class];
Class ClassObj3 = [obj2 class];
/
Class ClassObj4 = objc_getClass("NSObject");
Class ClassObj5 = object_getClass(obj1);
NSLog(@"class obj: %p, %p, %p, %p, %p",
ClassObj1,
ClassObj2,
ClassObj3,
ClassObj4,
ClassObj5);
//创建metaClass元类对象
Class metaClass1 = object_getClass([NSObject class]);
NSLog(@"metaClass obj: %p",
metaClass1);
看上边的代码,从内存
的角度来看
实例对象
在内存中的地址不相同
,可以得出每次实例经过 alloc,都会有自己的独占空间,是一个全新的对象
。类对象
无论用哪一种方式获得,从obj1
或者obj2
等等其他方式,最终拿到的内存地址都是相同,说明他们共享了一个类对象
。元类对象
从类对象
获得,说明也和实例无关,也是一个共享对象
。
类对象和元类对象都是Class类型
的,那么它们之间有什么联系呢?
NSObject
的元类对象
和类对象
在内存中都只有一份类对象
和元类对象
的类型相同,都是Class
类型Class
是一种结构体指针
,说明类对象
和元类对象
其实拥有相同的结构
从换取方式
来看,可以得到以下知识点:
-(Class)class
和+ (Class)class
这两种方式获得的都是类对象
objc_getClass
获取的也是类对象
Class object_getClass(id _Nullable obj)
比较复杂,传入instance对象
返回class对象
;传入class对象
返回meta-class对象
; 传入class对象
返回NSObject(基类)的 meta-class 对象
很重要的一张图!
- instance对象的isa指向class对象
- class对象的isa指向meta-class对象
- mate-class对象的isa指向基类的mate-class对象
- class的superclass指向父类的class
- 如果没有父类,superclass指针为nil
- meta-class的superclass指向父类的meta-class
- 基类的meta-class的superclass指向基类的class
struct objc_class的结构
先看一下它的代码:
struct objc_class {
// 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_rw_t的理解
rw代表可读可写
类中的属性、方法、协议等信息都保存在class_rw_t中
对class_ro_t的理解
ro代表只读
存储了当前类在编译期就确定了的属性、方法、协议