引言
众所周知,oc是面向对象编程,最重要的一个概念就是类,最终OC中的类都要编译成c++的,那么OC重的类在c++的底层是怎么呈现的呢?
打开#import<objc/objc.h>文件
/// An opaque type that represents an Objective-C class.
//一个不透明类型用于表示Objective-C类
typedef struct objc_class *Class;
/// Represents an instance of a class.
//表示类的实例
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
从上面代码可以看出,OC中的类(Class)编译后在c++中是以 struct objc_class呈现的,我们众所周知的NSObject类编译后在c++中是一个 struct objc_object,里面只有一个成员变量,即Class类型的变量isa。
在这里,我们也明白了为什么oc中的大部分对象可以用 id接收,因为id本身就是一个objc_object类型的结构体指针。
那么objc_object 和objc_class是什么关系呢?其实我们自定义(继承于NSObject)的类,编译成c++对应的就是objc_class。
查看源码runtime.h,发现结构体objc_class如下定义,这个是老版本OBJC1_(系统低于10.15)时候的定义
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
查看源码objc-runtime-new.h,结构体objc_class在新版本OBJC2_定义如下,isa是继承的父类objc_object里面的isa
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
...
}
下面我们重点研究isa,问题来了,想要研究isa,该从哪里入手?
我们前面研究了对象alloc流程的底层源码,方法调用顺序为:alloc--->_objc_rootAlloc--->callAlloc--->_objc_rootAllocWithZone--->_class_createInstanceFromZone.
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes); //计算对象内存大小
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size); //calloc 分配堆内存
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);//初始化对象的isa
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
代码中我们可以清晰的看到:_class_createInstanceFromZone做了三件事情。
- size = cls->instanceSize(extraBytes); //计算对象内存大小
- obj = (id)calloc(1, size); //calloc 分配堆内存
- obj->initIsa(cls);//初始化对象的isa
因此,initIsa就顺利成章的成为了我们研究isa的切入点。isa的初始化代码如下:
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;
}
从initIsa代码看出,该方法做了3件事。
1.初始化一个isa_t类型的newisa
2.newisa.bits位域的初始化
3.将newisa赋值给isa,完成isa的初始化工作
那么isa_t到底是什么类型?
union isa_t {
isa_t() { } //构造函数
isa_t(uintptr_t value) : bits(value) { } //构造函数
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
可以看出,isa_t是union(联合体),使用union也是基于内存优化,isa指针8字节,即64位,已经足够存储很多的信息了,由于union的特性互斥性(内存共用),极大得节省了内存。
提供了两个成员变量,cls和bits,由union的互斥性知只能存在一种形式初始化,那么根据什么判断呢?

不难看出,根据
nonpointer判断用哪种方式初始化。
还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD是一个宏定义。

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# ...
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
解释: : 1;、 : 44;分别表示该成员占1位、44位
下面我们来看不同位域的作用:
-
nonpointer:是否开启指针优化。0:纯isa指针只包含类对象地址,1:isa中包含了类对象地址、类信息、对象的引用计数等
还得回到_class_createInstanceFromZoneif (!zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { obj->initIsa(cls); }
由 obj->initInstanceIsa(cls, hasCxxDtor);调用的initIsa中nonpointer传的值为true
由 obj->initIsa(cls);调用的initIsa中nonpointer传的值为false。

nonpointer == NO时候,走if判断,即只对newisa的class设置。
nonpointer == YES时候,走else判断,对newisa多项赋值。
-
has_assoc:是否设置过关联对象,如果没有,释放时会更快。 -
has_cxx_dtor:是否含有c++的析构函数。 -
shiftcls:存储着Class、Meta-Class对象的内存地址信息,是我们研究的重点(arm64:33位,x86_64:44位) -
magic:判断当前对象是否进行了初始化。 -
weakly_referenced:是否被弱引用指向过。 -
has_sidetable_rc:判断是否需要用sidetable去处理引用计数(extra_rc的大小影响整个变量) -
extra_rc:里面存储的值是引用计数器减1
这里重点提到的是shiftcls,那么shiftcls存的地址信息和我们的类地址有什么关系呢?

从上面可以清晰的看到,shiftcls的地址 = WJPerson类地址 >> 3(向右移动了三位)得到的。