Eat,Code,Sleep

0%

NSObject 结构探究

NSObject

点开 NSObject 的头文件可以看到如下结构 :

1
2
3
4
5
6
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

再次点开 OBJC_ISA_AVAILABILITY 我们可以看到:

1
2
3
4
5
6
7
8
9
/* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable 
* in the future */
#if !defined(OBJC_ISA_AVAILABILITY)
# if __OBJC2__
# define OBJC_ISA_AVAILABILITY __attribute__((deprecated))
# else
# define OBJC_ISA_AVAILABILITY /* still available */
# endif
#endif

`isa` will be deprecated or unavailable : isa 将被废弃或不可用。

同时,编译器拓展 __attribute__((deprecated)) 也证实了该 isa指针在 OBJC2 不在推荐被使用,使用 objc->isa 访问时,编译器报错 “Direct access to Objective-C’s isa is deprecated in favor of object_getClass()” 。

事实上苹果早在 2006 年的 WWDC 就推出了 Objective-C 2.0 ,此处的 isa 的实现就变了,不再是 1.0 时那个指向类对象的指针了。

虽然在 Foundation 里面能找到的 objc_class 的被标记成OBJC2_UNAVAILABLE,但好在苹果在 OpenSource.apple.com 中放出了 objc 的相关代码。

在最新的 objc4-756.2 中我们可以找到的新的结构:

值得注意的是 objc4 系列的代码有新旧之分,例如 objc4-493.9 的结构和 objc4-756.2 就有较大出入,本文仅对当前最新的 objc4-756.2 做讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct objc_object {
private:
isa_t isa;

public:
// public function ...
}

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

class_rw_t *data() {
return bits.data();
}
// function ...
}

结构体 objc_class 是继承自 objc_object, 所以也包含 isa_t isa 。可以得到下图:

WX20200110-131156.png

ISA

class_diagram.png

关于 Instance/Class/MetaClass 的关系,很多博客习惯用这张图来说明。

事实上,在 arm64 之前(例如上文提到的 objc4-493.9 中),isa 的确就是一个指向

Class或者MeteClass对象的指针,而 arm64 之后,则需要 &ISA_MASK 掩码才能获得Class或者MeteClass 对象地址。

print_isa.png

不熟悉 LLDB 命令可以看 这里

上图中的 $0 和 \$1 并不相等,很好的论证了 isa 并没有指向 Class 对象。

顺带的我们用二进制输出一下,得到 \$2 和 \$3 .

注意,iOS 默认采用小端模式,数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,与我们的阅读习惯相反。 关于大小端相关的知识,可以看 这里

NSObject 的实现中,顺着 - (Class)class 的 LifeLine 一直找下去,最后来到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline Class 
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();

uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}

其中 isTaggedPointer 涉及到 Tagged Pointer 的知识,可以访问巧叔写的这篇 深入理解 Tagged Pointer

所以当不涉及到 Tagged Pointer 时,新的关系图应该是 :

class_diagram_new.png

即 isa 箭头应该 改为 ISA() 方法返回值。

ISA() 方法实现如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}

跳过断言 和 SUPPORT_INDEXED_ISA,可以看到该方法实际上返回的是 (Class)(isa.bits & ISA_MASK)

isa 是一个 union(联合体) ,在 __arm64__ 环境下其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;

struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
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 :是否 不是指针 ,1表示采用了 tagged pointer 优化;0表示未开启,此时 shiftcls 存放着 Class或者MetaClass 地址。
  • has_assoc :是否含有关联对象
  • has_cxx_dtor: 是否含有析构函数
  • shiftcls :Class或者MetaClass 地址
  • magic :是否已经初始化完成
  • weakly_referenced :对象被指向或者曾经指向一个 ARC 的弱变量
  • deallocating :是否正在释放
  • has_sidetable_rc :是否使用了散列表存储应用计数
  • extra_rc: 存储引用计数,值为引用计数 - 1 (引用计数为 10是,该值为9 ;引用计数过大时,has_sidetable_rc 为 1,引用计数存储在 sidetable 中)

而 __arm64__ 环境下 ISA_MASK 为 0x0000000ffffffff8ULL,在 LLDB 调试区输入 p/t 0x0000000ffffffff8ULL ,输出为 0b0000000000000000000000000000111111111111111111111111111111111000,与 \$3 按位与取得 3—35 位(下标从0开始)的数值,与 \$2 相等。

cache_t

上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

typedef unsigned long uintptr_t;

#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t SEL;

struct bucket_t {
private:
uintptr_t _imp;
SEL _sel;
//function ...
}

struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
//function ...
}

bucket_t的结构体中存储了 SEL 和 _imp 。分别存储了函数的编号和地址。

mask:分配用来缓存bucket的总数。

occupied:表明目前实际占用的缓存bucket的个数。

Cache的作用主要是为了优化方法调用的性能。使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

class_data_bits_t

class_data_bits_t 的结构体,其中只含有一个 64 位的 bits 用于存储与类有关的信息:

1
2
3
4
5
6
struct class_data_bits_t {

// Values are the FAST_ flags above.
uintptr_t bits;
// function ...
}

但是 在 objc_class 结构体中的注释写到 class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标志。

1
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

来看一下 class_data_bits_t 相关的结构:

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
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;
// function ...
}

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;

// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
// function ...
}

class_rw_t 存放着 方法、属性和协议等信息,其中 class_ro_t 存储着在编译期就已经确定的 ivars、属性、方法以及协议等信息。

Instance /Class /MetaClass

class_diagram_new.png

实例方法调用时,通过 ISA() 可以找到实例对象的 Class 对象,从而在 Class 对象中找到存放着的实例方法,Class对象中找不到时,通过superclass向父类查找,最终完成调用或抛出异常。

类方法 调用时,通过 ISA() 可以找到类对象的 MateClass 对象,从而在 MateClass 对象中找到存放着的类方法,MateClass 对象中找不到时,通过superclass向父类查找,最终完成调用或抛出异常。

参考资料