前言:
前面分析了对象创建时alloc是怎么申请内存空间,通过研究Class的第一个属性是isa,我们都知道isa的指向其类,对象的isa指向类Class, 那么对象创申请完内存空间之后是怎么和isa关联起来的呢?isa指针是怎么创建的呢?
isa指针的具体走向是什么呢?isa指针所占的8字节64位中具体存储了那些信息呢?接下来我们来探索一下:
1. isa指针结构
isa源码
#include "isa.h"
// 联合体 公用 联合体里面最大的那个内存
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_BITFIELD源码如下:
# 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)
可以看出在不通架构中isa内成员所占字节也不相同,这些成员所占字节之和刚好是64位,8字节。每个成员多占位数和存储内存如下:
nonpointer : 1字节;
表示是否对isa指针开启优化
0:纯isa指针 1:不止是类对象地址,包含了类信息、对象的引用计数等
has_assoc : 1字节;
关联对象标志,0:没有 1:存在
has_cxx_dtor : 1字节;
该对象是有有C++或objc的析构器,
有析构函数:做逻辑出来 无: 可以更快的释放对象
shiftcls : 33字节; MACH_VM_MAX_ADDRESS 0x1000000000
存储类指针的值 开启指针优化情况下,在arm64架构中用s33位来存储类指针
magic : 6;
调试器判断f当前对象是真的对象还是没有初始化的空间
weakly_referenced : 1;
标志对象是否指向或曾经指向一个ARC的弱引用,无弱引用可更快释放
deallocating : 1;
标志对象是否正在是否内存
has_sidetable_rc : 1;
当前对象引用计数大于10时,则需要借用该变量的存储进位
extra_rc : 19
表示该对象的引用计数值,实际是引用计数减1,
例: 当前对象引用计数为10,extra_rc = 9,如果引用计数大于10,
则需要使用has_sidetable_rc进位进位
2. isa的初始化
ias指针初始化分析如下:
isa是联合体,具有互斥性,对cls赋值,就不会对bits和isa内的结构体成员进行赋值。
在对新创建的isa_t newisa(0)进行赋值时,根据SUPPORT_INDEXED_ISA判断,对其进行不通的赋值。
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;
3. isa关联对象和类
上面提到isa指针中shiftcls存储类指针的值,通过打印类.class的值然后>>3,得到的值和通过对isa的shiftcls成员>>3,再>>20 <<20得到的值相等。
通过研究API object_getClass(object)
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
最终的返回结果是(Class)(isa.bits & ISA_MASK);源码如下:
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
}
代码验证:
LGPerson *object = [LGPerson alloc];
打印结果:
isa指针,通过对isa & 掩码 ISA_MASK 得到的值和打印对象的class一致。由此可以看出isa是通过shiftcls和关联到类的
4. isa的走位
同一个对象可以创建多个?同一个类可以创建多个吗?类只可以创建一个
类的内存,第一个位置必然是`isa`,指向`LGPerson`元类
对象是程序猿根据类实例化的
类是代码编写的,内存中只有一份,是系统创建的
元类是系统编译时,系统编译器创建的,便于方法的编译
对象的isa指向类对象,类对象的isa指向元类,元类isa指向什么呢?
通过打印类结构,分析如下:
对象isa -->类对象 --->元类-->根元类-->根元类
NSObject 根类 -> 根元类
经典isa走位图
isa:对象 -->类对象 --->元类-->根元类-->根元类superClass继承关系:NSObject 父类为nil,根元类的父类NSObject
5. 对象的本质
通过对下面代码的clang -rewrite-objc main.m -o mian.cpp生成的c++文件进行分析
@interface LGPerson : NSObject{
NSString *name;
}
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"123");
}
return 0;
}
结构体struct,通过property的属性,底层会自动生成setter和getter,并且会生成一个_属性的成员变量,底层编译不会生成相应的 setter和getter
6. union联合体补充
1. 什么是联合体
1. union中可以定义多个成员,union的大小由最大的成员的大小决定。
2. union成员共享同一块大小的
内存,一次只能使用其中的一个成员。 3. 对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数 相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆 盖掉,因为char只占一个字节,而int占四个字节) 4. union的存放顺序是所有成员都从低地址开始存放的。
2.联合体的好处
使代码存储数据高效率的同时,有较强的可读性,可以使用共用体来增强代码可读性,同时使用位运算来提高
数据存取的效率