0%

Runtime 源码笔记:对象与类

引言

同事突然指着Runtime(objc4-723)的源码objc-private.h中下面这段,问我:“objc_object结构体中只有一个isa变量,那对象中的实例变量去哪了?”

1
2
3
struct objc_object {
isa_t isa;
}

一下子有点儿懵逼,印象中的对象的内存布局总是如下图所示:

对象中实例变量的布局

声明一个RoundedRectangle类的实例对象变量,该变量指向对象中继承自NSObject类的isa实例变量,isa后紧跟着继承自其父类Shape中的fillColorbounds实例变量,之后是它自己的radius实例变量。

那么,这到底是怎么回事呢?

对象

我们在最初学习 Objective-C 时知道,所有的对象都包含一个叫isa的变量,该变量是一个指向对象所属类的指针,其定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
Class isa;
};

struct objc_class {
Class isa; //指向该类的 metaClass
#if !__OBJC2__ //以下在OBJC2(06年发布)版本后就废弃了
Class super_class; //指向该类的父类,若该类为根类时为NULL
const char *name; //类名
long version; //类的版本信息
long info; //类信息,运行时使用个位标识,如普通类、元类
long instance_size; //类的实例变量大小(包含继承自父类的实例变量)
struct objc_ivar_list *ivars; //类的成员变量地址列表
struct objc_method_list **methodLists; //类的方法地址列表
struct objc_cache *cache; //缓存的方法列表
struct objc_protocol_list *protocols; //遵循的协议列表
#endif
};

id是一种特殊的类型,它能指代任意的 Objective-C 对象类型。对象是由objc_object结构体来定义的。其中包含一个Class类型的变量isaClass是一个指向objc_class结构体的指针,在这个结构体中存放着类的“元数据”。

Tagged Pointer

然而在处理器从32位迁移到64位后,对象指针扩大为64位的整数,为了使地址内存对齐,一些位将永远是零。出于节省内存和提高运行效率的目的,苹果爸爸在WWDC 2013 Session 404中提出了 Tagged Pointer 的概念,因此也修改了objc_object的定义。

Tagged Pointer

Tagged Pointer2

当对象指针的最低有效位(LSB)为 1 时,则该指针为 Tagged Pointer。其实这个指针的值本质上已经不是指向对象的地址了,而是保存着对象数据的值类型的变量。所以它的内存并不存储在堆中,也不需要 malloc/free。

我们再看一下objc_object的定义的变化:

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
struct objc_object {
private:
isa_t isa;
public:
Class ISA(); //非 tagged pointer 对象获取其 Class 的函数
Class getIsa(); //tagged pointer 对象获取其 Class 的函数
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
Class changeIsa(Class newCls);
//...
}

union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
//这里只提取了__x86_64__架构的结构体定义,其他如__arm64__等架构类似。
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1; //标志位:0表示原生指针,1表示使用 taggedPointer 优化内存
uintptr_t has_assoc : 1; //是否绑定过关联对象,若没有可快速释放
uintptr_t has_cxx_dtor : 1; //是否有析构函数,若没有可快速释放
uintptr_t shiftcls : 44;//所属类指针的非零位
uintptr_t magic : 6; //固定值0xd2,调试器用来分辨对象的
uintptr_t weakly_referenced : 1; //是否被 __weak 变量引用过,若没有可快速释放
uintptr_t deallocating : 1; //是否正在释放
uintptr_t has_sidetable_rc : 1; //引用计数值是否过大
uintptr_t extra_rc : 8; //对象的引用计数值-1(若值为5,则对象引用计数为6)
};
}

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);
//这里截取SUPPORT_INDEXED_ISA = 0 时的代码,查知只有在watchABI的环境下SUPPORT_INDEXED_ISA值才为1
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;

isa = newisa;
}
}

isa变量的类型由Class类型变为了isa_t的类型,这个类型是一个联合(union),在联合中,几个字段共用同一块内存,其长度为联合中最大字段的长度。在initIsa方法中,当不启用 Tagged Pointer 时,就直接使用类的指针为isa.cls赋值,它还是表示对象所属类的指针。当启用时,使用宏ISA_MAGIC_VALUE来初始化isa.bits,这样已经为结构体内的magicnonpointer字段赋值了,然后根据函数参数设置has_cxx_dtor,将类的指针右移 3 位以消除用于内存对齐所补的 0,使用非 0 位来为shiftcls字段赋值。

类与元类

无论是否启用 Tagged Pointer,isa变量中都保存了对象所属类的信息。接下来我们看一下定义类的结构体objc_class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 缓存指针和 vtable,加速方法调用
class_data_bits_t bits; // class_rw_t类型的结构体指针加 retain/release/alloc/dealloc 路径优化的标记位

class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//...
}

objc_class是继承自objc_object的,因此类本质上也是对象,称为类对象。类对象中包含继承来的isa变量,与实例对象中isa变量“指向”对象的所属类类似(这里的指向指的是变量中保存了对象所属类的信息,可以通过此信息找到类,下文中均如此表述),类对象的isa指向该类的元类(metaclass)。这一点可以从创建类与元类的函数objc_allocateClassPair中看出:

动态创建类与元类

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
Class objc_allocateClassPair(Class superclass, const char *name, 
size_t extraBytes)
{
Class cls, meta;
//加锁
rwlock_writer_t lock(runtimeLock);
//检查类名是否已经存在,验证父类是否合格(已实现的构建完成的类或者创建根类时可为空)
if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}
//为类与元类开辟空间
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
//初始化类与元类
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
//本来不想贴这长串代码,但是细读对理解类的创建过程的有所理解
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
runtimeLock.assertWriting();

class_ro_t *cls_ro_w, *meta_ro_w;
//分配class_rw_t和class_ro_t结构体空间
cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
cls->data()->ro = cls_ro_w;
meta->data()->ro = meta_ro_w;

// 设置class_rw_t结构体内基本信息
//类已分配但未注册|class_rw_t->ro是class_ro_t在堆中的拷贝|class_t->data是class_rw_t不是class_ro_t|类已开始实现但未完成
cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
cls->data()->version = 0;
meta->data()->version = 7;

//设置cls_ro_t结构体内基本信息
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;//元类
if (!superclass) {
cls_ro_w->flags |= RO_ROOT;//根类
meta_ro_w->flags |= RO_ROOT;
}
if (superclass) {
//设置cls_ro_t->instanceStart和cls_ro_t->instanceSize为父类的instanceSize
cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize();
cls->setInstanceSize(cls_ro_w->instanceStart);
meta->setInstanceSize(meta_ro_w->instanceStart);
} else {
cls_ro_w->instanceStart = 0;
meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa
meta->setInstanceSize(meta_ro_w->instanceStart);
}
//设置类名与元类名
cls_ro_w->name = strdupIfMutable(name);
meta_ro_w->name = strdupIfMutable(name);
//初始化储存属性内存管理特性的值
cls_ro_w->ivarLayout = &UnsetLayout;//记录__strong的实例变量
cls_ro_w->weakIvarLayout = &UnsetLayout;//记录__weak的实例变量
//设置元类与类的索引
meta->chooseClassArrayIndex();//此处函数中逻辑只在 SUPPORT_INDEXED_ISA = 1 时编译
cls->chooseClassArrayIndex();

// 分别为类与元类添加它们与父类和元类的联系
cls->initClassIsa(meta);
if (superclass) {
meta->initClassIsa(superclass->ISA()->ISA());
cls->superclass = superclass;
meta->superclass = superclass->ISA();
addSubclass(superclass, cls);
addSubclass(superclass->ISA(), meta);
} else {//没有父类,则该类为根类
meta->initClassIsa(meta);//根类的元类指向元类本身
cls->superclass = Nil;//根类的父类为 Nil
meta->superclass = cls;//根类的元类的父类指向根类
addRootClass(cls);
addSubclass(cls, meta);
}
//初始化方法缓存列表为空
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
}

cls->initClassIsa(meta);可以看出类对象cls确实使用元类对象meta初始化了它的isa变量。接下来的代码在该类有没有父类的情况下分别建立起了类与元类与他们各自父类与元类的联系。superclass指针确立了继承关系,isa描述了实例所属的类,通过这些联系建立起来了”类的继承体系”,如下图所示,通过这张布局关系图,我们可以查出对象能否响应某个选择子,是否遵从某项协议等,并且能够通过isMemberOfClassisKindOfClass等类型信息查询方法来检视类的继承体系。

objc-isa-class-diagram

需要注意以下两点:

  1. 所有的 metaclass 的isa都指向根类的元类,包括根元类!这样就形成了一个闭环。当向对象(类对象/元类对象)发送消息时,runtime 会在对象所属的类的方法列表里面查找消息对应的方法,这样的闭环会保证这一步执行正确。
  2. 元类的父类指向类的父类的元类(绕口令啊(ノ ゚Д゚)ノ ┻━━┻),例外的是根类,根类的 superclass为 nil,根类的元类的superclass指向根类。当在对象所属类的方法列表中没有找到对应的方法时,runtime 会去类的父类中查找,如果找到了就跳转到方法的实现代码中,如果一路向上找到根类也没有找到时,runtime 的”消息传递机制”就结束了。接下来会启动“消息转发机制”,详情可查看这里:【翻译】Objective-C Runtime Programming Guide

我们说类对象结构体objc_class中存放着类的“元数据”,例如类的实例实现了哪些方法,具备多少个实例变量及其布局等信息,而元类中保存了类对象本身所具备的元数据,“类方法”就定义在这里,因为这些方法可以理解成类对象的实例方法。objc_class结构体中的isasuperclass变量构建起了类的继承体系,接下来我们看看剩下的部分。

cache_t

objc_class结构体中的cache字段主要用于缓存调用过的方法。当对象接收到消息时,runtime 根据isa去对象所属类的方法列表中查找,如果每次都经过这样的流程,方法调用的效率会比较差,因此在第一次调用过一个方法后,这个方法就会被缓存在cache中,下次接收到消息时会先在这里查找,没有才去方法列表中查找。

1
2
3
4
5
6
7
8
9
10
11
12
struct cache_t {
struct bucket_t *_buckets; //一个用于存储缓存方法的散列表
mask_t _mask; //_mask+1 = 目前分配的用来缓存方法的 bucket 的总数
mask_t _occupied; //目前实际占用的 bucket 数量
//...
}

struct bucket_t {
private:
cache_key_t _key; //方法选择子对应的 key
IMP _imp; //方法实现的函数指针
};

class_data_bits_t

objc_class结构体中的bits字段的注释为:”class_rw_t * plus custom rr/alloc flags”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define FAST_IS_SWIFT           (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<1)
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
#define FAST_DATA_MASK 0x00007ffffffffff8UL

struct class_data_bits_t {
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
//...
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
bool hasCxxCtor() {
return getBit(FAST_HAS_CXX_CTOR);
}
//...
}

可以看出这个结构体中只有一个 64 位的值bits,该数据中保存了一个指向class_rw_t结构体的指针和是否为 swift 类、是否有默认的retain/release等方法及是否要求 raw isa 三个标志位。结构体中还提供了使用掩码来访问这些数据的方法(甚至还提供一些对class_rw_t结构体中flags字段的访问函数)。引用深入解析 ObjC 中方法的结构文章中的配图可以看得更明白一些:
class_data_bits_t

class_rw_t

class_data_bits_t结构体中的data()方法返回了指向class_rw_t结构体的指针,这个结构体中保存了类的方法、属性和协议等信息。先看一下它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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; //取消名字修饰后的类名
//省略 SUPPORT_INDEXED_ISA = 1 时的 index 字段
}

class_rw_t中还包含一个与它类似的结构体class_ro_t的常量指针。从命名我们可以猜出class_ro_t中存储的是只读的信息,而class_rw_t中存储了可读写的信息。实际上也是如此,在class_ro_t中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议等信息,class_rw_t存储的内容是可以动态修改的,所以运行时对类的扩展大都存储在这里。先看下class_ro_t的定义:

class_ro_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct class_ro_t {
uint32_t flags; //标记信息
uint32_t instanceStart; //类自己定义的实例变量的起始偏移量
uint32_t instanceSize; //类的总的实例变量大小
#ifdef __LP64__
uint32_t reserved; //保留的数据
#endif

const uint8_t * ivarLayout; //__strong修饰的实例变量

const char * name; //类名
method_list_t * baseMethodList; //方法列表
protocol_list_t * baseProtocols; //协议列表
const ivar_list_t * ivars; //实例变量列表

const uint8_t * weakIvarLayout; //__weak修饰的实例变量
property_list_t *baseProperties; //属性列表

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

在上面的“创建新的类与元类”部分,我们看到在初始化类与元类的函数objc_initializeClassPair_internal中,开辟了class_rw_tclass_ro_t的存储空间,并将返回的指针保存在相应的数据中。接着对class_rw_t中的flags,version,ro字段进行了初始化,在addSubclass(superclass, cls)方法中又设置了自身的nextSiblingClass以及父类的firstSubclass。同时初始化了class_ro_t中的flags,instanceStart,instanceSize,ivarLayout,weakIvarLayout,name等字段(注意当存在父类时,本类的instanceStartinstanceSize都使用父类的instanceSize进行了初始化)。

我们上面不是说过class_ro_t结构体中的值是只读的嘛?为什么在这里对其中的变量进行了设置?而且如果你阅读过Objective-C Runtime函数列表,就会发现class_addIvar这个函数也是在运行时通过修改类的class_ro_t中的字段,向类中添加了实例变量。

这是因为这些操作都是发生在动态构建类的过程中!class_addIvar的文档中有这么一段话:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

也就是说一旦完成类的构建过程,就不能再修改class_ro_t中的内容了,当然也就不能向其中添加实例变量了。objc_registerClassPair中做的事情就是将类的class_rw_t中的flags标记的RW_CONSTRUCTING | RW_REALIZING标志位清除,同时设置RW_CONSTRUCTED这个表示类已经构建完成的标志位。

类的加载过程

在上面一部分,我们通过阅读创建类与元类的函数objc_allocateClassPair对动态创建类的过程有了一定的理解,并且认识了表示类的结构体及其初始化的过程。但是对于我们在源代码中编写的类,它的加载过程是怎样的呢?

我们编写的源代码在编译结束后链接的过程中就静态链接进程序的二进制文件中了,而程序中引入的系统库如”Foundation.framework”和包含 Runtime 的”libobjc.A.dylib”等则是通过苹果的动态链接器-dyld(the dynamic link editor)在程序启动时动态加载的。我们看一下 Runtime 的初始化入口方法_objc_init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

//各种初始化
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
//注册dyld事件的回调函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

在这个函数的最后注册了 dyld 相关事件的回调函数,当 ImageLoader 读取了 images(可执行文件或动态库等)并将其加载进内存后,dyld 会调用回调函数map_images对其进行解析和处理。接下来当需要对该 image 进行初始化时,dyld 就会调用回调函数load_images对其初始化。

map_images

map_images中提取了 images 中 ObjC 相关的元数据(Class、Selector、Protocol等符号)进行了初始化,主要过程发生在_read_images函数中:

  1. 针对包含有 swift 旧版本代码和对 sdk 版本在 OS X 10.11之前的进行disableTaggedPointers操作。

  2. 初始化一个全局的映射表gdb_objc_realized_classes用来存储没有在 dyld 共享缓存中优化过的 Classes,注意这个名字中的 realized 属于误用,其中存放的类也可以是未实现的状态。

  3. readClass : 将类与元类照编译器编译好的方式从二进制中读出来,然后将不重复的非元类插入到gdb_objc_realized_classes中,该函数的返回值有3中情况:

    • cls:即类的指针,注意如果该类之前被 alloc 为未来实现的类则需要拷贝一个新类并将rw的类型class_rw_t强制转换为class_ro_trw->ro 赋值,设置新的rw并将其添加进 remappedClasses,注意这里的newCls就已经是realized的状态了。代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class_rw_t *rw = newCls->data();
    const class_ro_t *old_ro = rw->ro;
    memcpy(newCls, cls, sizeof(objc_class));
    rw->ro = (class_ro_t *)newCls->data();
    newCls->setData(rw);
    freeIfMutable((char *)old_ro->name);
    free((void *)old_ro);

    addRemappedClass(cls, newCls);

    replacing = cls;
    cls = newCls;
    • nil:当类没有父类或者当前父类是弱连接的,将这个类添加进需要重新映射的表后返回 nil.
    • something else: 指向保留的用于为未来实现的类开辟的空间.
  4. 修正上一步中添加进remappedClasses的类,用已经实现的新类替换旧类的引用。

  5. 修正 selector 的引用。

  6. 读取 protocals 以及修正其引用。

  7. 对于实现了+load方法或者有静态实例的 non-lazy classes 执行realizeClass来实现它(注意对于 lazy classes 则是在其首次接收到消息时才实现它),返回类真实的数据结构使其处于可用的状态,关键代码如下所示:

    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
    //将 ro 替换为 rw,标记类为 已实现|实现中
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
    }
    //查询是否为元类
    isMeta = ro->flags & RO_META;
    //设置version
    rw->version = isMeta ? 7 : 0; // old runtime went up to 6

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex(); //仅在watchABI下有效

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()));

    //...省略SUPPORT_NONPOINTER_ISA时的代码

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
    cls->setHasCxxDtor();
    if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
    cls->setHasCxxCtor();
    }
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
    addSubclass(supercls, cls);
    } else {
    addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls);
    return cls;

    在上面的reconcileInstanceVariables过程中,在ro->instanceStart < super_ro->instanceSize情况下即父类扩展了它的实例变量使得实例变量大小增大时,校准了ro->instanceSizero->instanceStartivars容器中每个ivar->offset指向的值以实现non-fragile instance variables的功能!

    methodizeClass过程中将ro->baseMethods(),ro->baseProperties,ro->baseProtocols分别添加进rw->methods,rw->propertiesrw->protocols容器列表中,然后将通过unattachedCategoriesForClass获取的未附加的分类附加在类上。

  8. 读取 Category ,然后通过addUnattachedCategoryForClass将分类注册到它的所属类上,之后调用remethodizeClass将实例方法、协议和属性添加到类上,将类方法添加到元类上。需要注意的是:1,Category 的方法没有“完全替换掉”原来类已经有的方法,也就是说如果 Category 和原来类都有methodA,那么 Category 附加完成之后,类的方法列表里会有两个methodA;2,Category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会返回。

load_images

load_images方法就是调用+load方法,看一下prepare_load_methods方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
classref_t *classlist = 
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}

category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}

这个函数基本就是准备好实现了+load方法的类和分类,将其分别添加到 loadable_list 中。需要留意的是在schedule_class_load方法中会递归调用schedule_class_load(cls->superclass),来保证先将父类添加进 loadable_list 中。

准备好后就调用了call_load_methods,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}

// 2. Call category +loads ONCE
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);

需要注意的是:

  • 类的+load方法是先于分类调用的!但是在类的+load方法中可以调用该类的分类中声明的方法,因为在此之前分类就已经 attach 到这个类上面了。
  • call_class_loads从 loadable_list 中依次取出 class 然后调用+load,这里保证了父类优先调用的顺序!
  • +load方法是直接使用函数内存地址的方式调用的:(*load_method)(cls, SEL_load),它对于实现了+load方法的添加进 loadable_list 中的每个类与分类都会调用。因此这里成了极佳的Method Swizzling的时机。但是分类之间的+load执行顺序是按照编译顺序决定的,因此不同的编译顺序会导致分类间的+load顺序不固定。类的载入顺序不同也导致类之间+load方法执行顺序不固定。
  • 如果代码还依赖了其他程序库,那么其他程序库里面的相关类的+load方法会先执行。
  • 留意与+load类似的+initialize方法,可以执行类的初始化操作,不同的是调用这个方法时,运行时系统已经处于正常状态了,在这里可以调用任意类的任意方法。+initialize方法是惰性调用的,在程序首次使用类的时候才会调用一次它,这个方法是线程安全的,执行时会堵塞其他线程。还有与+load不同,+initialize遵循普通的继承与覆写规则,当类没有实现这个方法时,会调用其父类的实现。

对象的创建

接下来简单看下对象的创建过程,我们通过调用[[XXXClass alloc] init]来生成一个实例对象。

alloc

在代码[[NSObject alloc] init]中添加一个断点,查看一下调用栈:
alloc

初始化对象的关键代码都在_class_createInstanceFromZone方法中,抽取出一个主要流程的代码如下:

1
2
3
4
5
6
7
8
9
    //获取实例对象内存对齐后的大小
size_t size = cls->instanceSize(extraBytes);
//分配存储空间
obj = (id)calloc(1, size);
if (!obj) return nil;
//初始化isa,这部分代码我们再文章上面可以看到
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}

init

1
2
3
4
5
6
7
8
9
10
11
- (id)init {
return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}

可以看到init操作只是简单的返回了对象背身。

最后

看完了这些我们也能回答引言中提出的问题了:Objective-C对象不能简单对应于一个C struct,访问成员变量不等于访问C struct成员,当生成对象时,开辟出来对象的instanceSize大小的内存区域并返回指向该空间的指针,在这个内存空间中不仅包含了objc_object中定义的isa还有其他所有的实例变量(包括继承自父类的及其自己的),我们通常所说的类的实例对象也就指的是这块内存空间。

参考