前言
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
一个最基础的NSObject对象大小为8字节,而这个8字节就是我们时常听闻的ISA,本文将记录探索ISA究竟是什么的过程。
获取main.cpp
OC代码相对信息不全,且有很多代码并不好找,将main.m文件编译获得main.cpp文件可以帮助我们获得更多的信息进行分析。
具体操作即到main.m目录下使用如下指令。
main.cpp分析 类对象的本质
示例代码,定义一对有继承关系的类
@interface Father : NSObject
@property (nonatomic, assign) int age;
@end
@interface Son : Father
@property (nonatomic, strong) NSString *sonName;
@end
获得arm64架构下对应的cpp文件,由于文件过大,我们直接进行关键代码分析:
- 找到
Father和Son的定义,发现cpp文件里是用struct对类进行定义,其中NSObject结构体内的唯一成员就是我们关心的Class isa。
struct NSObject_IMPL {
Class isa;
};
// Father继承了NSObject
struct Father_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
// Son继承了Father
struct Son_IMPL {
struct Father_IMPL Father_IVARS;
NSString *_sonName;
};
- 找到
Class的定义,发现就是objc_class *类型的别名,即ISA本质上就是一个结构体objc_class的指针。而结构体objc_class类型在cpp文件中仅有声明,并没有具体定义,其定义需要从objc源码中去进一步探究。
typedef struct objc_class *Class;
其实我们多观察cpp文件能看到不少我们之前一知半解的地方,比如id类型之所以能代表通用类型且不用加*号,就是因为id就是objc_object *的别名。
typedef struct objc_object *id;
又或者说我们之所以能通过变量名字前加_访问属性,是因为属性实质上就是OC类对应结构体内的成员变量,其变量名默认_+属性名,感兴趣的话还可以找一下属性对应的getter setter方法也在cpp文件中有对应的定义。
结论
OC中类的本质是结构体,因为OC中所有类都继承自NSObject,而NSObject的结构体定义中有且仅有成员Class isa;(Class即objc_class *),所以我们通常说isa是每一个对象用来标识自身类型的。
objc源码分析 objc_class是什么
- 在
objc源码中找到objc_class定义,我们看到它继承自objc_object并且有三个成员变量。
struct objc_class : objc_object {
...
// Class ISA;
Class superclass; // 指向父类的isa对象
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
- 找到
class_object定义,发现其只有一个成员变量isa_t isa;,这里的isa和上文中的Class isa;的关系会在后面进一步探究。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA(bool authenticated = false);
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
};
结论
上文说OC类的本质是一个结构体,其内部一定有一个Class isa,而通过objc源码,我们对OC类又有了新的理解:OC中所有的类最终即objc_object,而OC又定义了其的子类型objc_class来存储父类,方法缓存等信息以更好的使用OC类。
isa_t
上文中我们最后发现objc_object中存在唯一成员变量isa_t isa,它与NSObject中的唯一成员变量Class isa同名但不同类型,那么他们究竟是和关系呢?
在源码中查看isa_t的定义,发现它是一个位域union,有两个成员变量为uintptr_t bits和Class cls,而我们知道Class就是一个指针,所以这两个成员变量大小相同,本质上就是同一段内存。
对于位域联合体不了解的建议自行补习一下。
同时,之前的问题也有了答案,isa_t是包括Class类型的联合体,两者本质上是同一段内存。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// !! 重点 !!
Class cls;
public:
// 位域定义
#if defined(ISA_BITFIELD)
struct {
// !! 重点 后面会分析 !!
ISA_BITFIELD; // defined in isa.h
};
...
#endif
...
};
那么新的问题出来了,为什么OC要定义联合体isa_t来兼容Class呢,uintptr_t bits又是干什么用的呢?
这就涉及一个概念nonPointerIsa,而bits就可以理解为nonPointerIsa。
nonPointerIsa
nonPointerIsa即非纯指针isa,与之对应的Class isa就是纯指针。具体点解释,nonPointerIsa表示该isa变量内存除了存储指针内容外,还存储了其他信息,指针信息只占用其一部分内存来表示。
nonPointerIsa其实我们并不陌生,回顾之前遇到它是在探究alloc的时候,代码如下:
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)
{
...
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
...
id obj;
...
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor); // objc_object::initIsa(cls, true, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls); // objc_object::initIsa(clas, false, false)
}
...
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor);
现在再看这段代码,我们能发现在初始化ISA的时候,fast变量即标识类的ISA类型是nonpointer非纯指针类型的。我们具体看下面initIsa内容会发现,对于纯指针类型的初始化,我们将cls简单复制即可,针对nonpointer我们还会进行一些其他的赋值操作。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} 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);
newisa.extra_rc = 1;
}
isa = newisa;
}
接下来我们具体看nonPointerIsa中除了指针还存储了什么信息,这部分内容其实就定义在了isa_t定义中的位域信息中:
# if __arm64__
# 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; // 是否有c++的析构函数 \
uintptr_t shiftcls : 33; // 存储类指针的值 \
uintptr_t magic : 6; // 用于调试器判断当前对象是真的对象还是未初始化的空间 \
uintptr_t weakly_referenced : 1; // 标志对象是否被指向或曾经指向一个ARC的弱变量 没有弱饮用的对象可以更快释放 \
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)
ISA_BITFIELD即之前isa_t定义中用到的位域信息,其中各位的存储信息都通过注释进行解释了。
通过ISA推导Class类型
接下来我们本地调试验证一下ISA中存储的信息是否如我们探究的。
如图所示,我们将
ISA & ISA_MASK后即Father的类指针,能通过输出识别。