iOS NSObject对象模型

前言

我们编写的Objective-C代码,底层通过C/C++代码实现。

Objective-C -> C/C++ -> 汇编语言 -> 机器语言

那么一个Objective-C类、对象是如何通过C/C++实现、储存的呢?

源码探究

ISA

我们通过以下命令,将.m文件转化为C/C++代码。

1
clang -rewrite-objc **.m

可以获取 **.cpp文件。

在7700多行,可以获取如下结构体

1
2
3
struct NSObject_IMPL {
Class isa;
};

在objc.h头文件中,我们可以发现如下代码

1
2
3
4
5
6
7
8
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

通过注释我们得知:

  • 结构体objc_class表示一个Objective-C的类。
  • 结构体objc_object表示一个Objective-C Class的实例。

那么objc_class又是什么呢?

在runtime.h文件中,我们可以发现如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
/* Use `Class` instead of `struct objc_class *` */

我们可以得知:在Objective-C中,每个对象都是一个结构体,都有一个isa指针,类对象Class也是一个对象。在运行时,可以通过isa指针查找该对象是什么类。


那么isa到底是什么?为什么需要isa呢?

我们可以通过objc4源码获取一些信息

objc4 的源码不能直接编译,需要配置相关环境才能运行。可以在这里下载可调式的源码。
objc 运行时源码的入口在 void _objc_init(void) 函数。

objc-private.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_object {
private:
//isa是一个union联合体,包含这个对象所属类的信息
isa_t isa;

public:

// ISA() assumes this is NOT a tagged pointer object
Class ISA();

// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
}

objc-runtime-new.h

1
2
3
4
5
6
7
8
9
10
11
12
13
struct objc_class : objc_object {
// Class ISA; (ISA继承于objc_object)
Class superclass; //当前类父类
cache_t cache; // formerly cache pointer and vtable 缓存指针和vtable,提高方法调用效率
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 存储类的方法、属性、协议等信息

// 针对 class_data_bits_t 的 data() 函数的封装,最终返回一个 class_rw_t 类型的结构体变量
// Objective-C 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
class_rw_t *data() {
return bits.data();
}
...
}

通过源码可以得知,objc_class继承于objc_object,所以二者都存在isa成员变量,类型为:isa_t。isa_t是一个union。

我们回到objc-private.h中

1
2
3
4
5
6
7
8
9
10
11
12
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_t包含一个成员变量 cls。

每个objc_object通过自己持有的isa查找到自己所属的类。而对于objc_class来说,可以通过isa找到自己的mate class,即元类。

对于IAS_BITFIELD,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//__arm64__ 
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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
  • nonpointer

表示 isa_t 的类型,0 表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。1 表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。

  • has_assoc

对象含有或者曾经含有关联引用(category相关),没有关联引用的可以更快地释放内存(object dealloc相关,其他文章会说到)。

  • has_cxx_dtor

表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存(object dealloc相关)。

  • shiftcls

见nonpointer

  • magic

用于调试器判断当前对象是真的对象还是没有初始化的空间

  • weakly_referenced

对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放

  • deallocating

对象正在释放内存

  • has_sidetable_rc

对象的引用计数太大了,存不下

  • extra_rc

对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9。


isa初始化

我们可以通过isa初始化方法,来了解这64位bits作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
inline void 
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}

inline void
objc_object::initClassIsa(Class cls)
{
if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) {
initIsa(cls, false/*not nonpointer*/, false);
} else {
initIsa(cls, true/*nonpointer*/, false);
}
}

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;
}
}

当我们初始化一个Objective-C对象,为其分配内存时,调用栈中包含了 initInstanceIsa与initIsa这2个方法。

initInstanceIsa方法,传入的nonpointer为true。所以initIsa方法可以简化为(ARM_ARCH_7K应该是watch指令集,所以我们只看else部分):

1
2
3
4
5
6
7
8
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}

先将 newisa 的 bits 赋值为常量 ISA_MAGIC_VALUE

1
#define ISA_MAGIC_VALUE 0x000001a000000001ULL

里面包括了 magic 和 nonpointer 的值。然后将是否有 C++ 析构函数标示上,最后将位移(shift)后的 cls 存入 shiftcls。

1
newisa.shiftcls = (uintptr_t)cls >> 3;

将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。

绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。

最后将 isa = newisa,工作就结束了。

方法、属性、协议

在Objective-C中,对象的方法储存在类中,而非实例对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;

const class_ro_t *ro;

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

Class firstSubclass;
Class nextSiblingClass;

char *demangledName;

#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif

void setFlags(uint32_t set)
{
OSAtomicOr32Barrier(set, &flags);
}

void clearFlags(uint32_t clear)
{
OSAtomicXor32Barrier(clear, &flags);
}

// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
assert((set & clear) == 0);

uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 用于存储一个 Objective-C 类在编译期就已经确定的属性、方法以及遵循的协议
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

const uint8_t * ivarLayout;

const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;
property_list_t *baseProperties;

method_list_t *baseMethods() const {
return baseMethodList;
}
};

当一个对象的实例方法被调用时,需要先通过持有的isa指针查找相应的类,然后在类的class_data_bits_t 结构体中查找对应方法的实现(每个对象可以通过 cls->data()-> methods 来访问所属类的方法)。同时,每一个 objc_class 也有一个指向自己的父类的指针 super_class 用来查找继承的方法。

而当一个类的类方法被调用时,通过类的isa在元类中获取方法的实现。如上图。

结论

isa用于查找对象(或类对象)所属类(或元类)的信息,比如方法列表、属性、协议等。

ivar_t

protocol_t

method_t