RunTime 简介
-
什么是 RunTime?
我们都知道,将源代码转换为可执行的程序,通常要经过 5 个步骤:预处理、编译、汇编、链接、运行。不同的编译型语言,在这 5 个步骤中所进行的操作又有些不同
C 作为一门静态语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现
Objective-C 作为一门动态语言,在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的是哪个方法。只有在运行期间才检查变量的数据类型,同时在运行期间才会根据方法名查找要调用的具体方法。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么
Objective-C 把一些决定性的工作从(编译阶段、链接阶段)推迟到(运行时阶段)的机制,使得 Objective-C 变得更加的灵活。我们甚至可以在程序运行的时候,动态地去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性
实现 Objective-C 运行时机制的一切基础就是 RunTime。RunTime 实际上是一个动态库,这个动态库使我们可以在程序运行时动态地创建对象、检查对象,修改类和对象的方法
实例对象 && 类对象 && 元类对象
-
Object(实例对象)
Object(实例对象)被定义为指向
struct objc_object的指针,其数据结构如下:// A pointer to an instance of a class.(指向一个类的一个实例的指针) // Objective-C 中的 id 类型,被定义为一个指向 struct objc_object 的指针,即被定义为一个指向实例对象的指针 typedef struct objc_object *id; // Represents an instance of a class.(表示一个类的一个实例) struct objc_object { Class _Nonnull isa; // 指向该实例对象所属的类 // 编译时,根据开发者所定义的类的不同,编译器会以 struct objc_object 为模板,生成不同的 struct XXX_IMPL,并在 struct XXX_IMPL 中添加该类的实例对象所特有的成员变量 // 成员变量 0 // 成员变量 1 // 成员变量 2 // ... };当对一个实例对象进行成员变量访问时,比如
receiver->_ variable = 10;,RunTime 会根据成员变量在实例对象(struct objc_object)中的偏移量,找到相应的成员变量,然后进行访问当对一个实例对象进行方法调用时,比如
[receiver selector];,RunTime 会通过实例对象(struct objc_object)的isa指针找到对应的类对象(struct objc_class),然后在类对象(struct objc_class)的methodLists(方法列表数组)中查找相应方法的地址,然后跳转执行 -
Class(类对象)
Class(类对象)被定义为指向
struct objc_class的指针,其数据结构如下:// An opaque type that represents an Objective-C class.(一个不透明的类型,用于表示 Objective-C 中的一个类) typedef struct objc_class *Class; struct objc_class { Class _Nonnull isa; // is-a 指针,对象的 is-a 指针指向类对象,类对象的 is-a 指针指向元类对象,元类对象的 is-a 指针指向根元类对象 #if !__OBJC2__ Class _Nullable super_class; // 父类 const char * _Nonnull name; // 类名 long version; // 类的版本信息,默认为 0 long info; // 类的信息,供运行时使用的一些标识位,如 CLS_CLASS(0x1L) 表示类对象,其中包含对象方法和成员变量。CLS_META(0x2L) 表示元类对象,其中包含类方法 long instance_size; // 该类的实例变量大小 struct objc_ivar_list * _Nullable ivars; // 该类的成员变量链表(一维) struct objc_method_list * _Nullable * _Nullable methodLists; // 该类的方法列表链表(二维) struct objc_cache * _Nonnull cache; // 该类的方法缓存 struct objc_protocol_list * _Nullable protocols; // 该类遵守的协议链表 #endif };struct objc_class中存储的是(用于描述类简要信息)与(用于描述实例对象详细信息)的变量,即struct objc_class存放的都是元数据(meta-data)struct objc_class的第一个成员变量是isa指针,在 Objective-C 体系结构中,isa指针用于表示一个对象所属的类。换句话说,在 Objective-C 中类的本质也是一个对象,称之为类对象在 Objective-C 中成员变量与属性都是依附在实例对象上的,并且在 Objective-C 中不存在所谓的 类成员变量 与 类属性
当对一个类对象进行方法调用时,比如
[Receiver selector];,RunTime 会通过类对象(struct objc_class)的isa指针找到对应的元类对象(struct objc_class),然后在元类对象(struct objc_class)的methodLists(方法列表数组)中找到相应方法的地址,然后跳转执行 -
Meta-Class(元类对象)
在 Objective-C 中:
一个 实例对象 所属的类叫做 类对象,用于描述实例对象本身所具有的特征
一个 类对象 所属的类叫做 元类对象,用于描述类对象本身所具有的特征在 Objective-C 中,
isa指针用于表示一个对象所属的类:
实例对象(struct objc_object)的isa指针指向类对象(struct objc_class)
类对象(struct objc_class)的isa指针指向元类对象(struct objc_class)
元类对象(struct objc_class)的isa指针指向根元类对象(struct objc_class)
因此,元类对象也用struct objc_class表示。与类对象不同的是,元类对象的struct objc_class结构体中存储的是(用于描述类对象详细信息)的变量对象方法的调用过程为:通过实例对象的
isa指针找到其所属的类对象,在类对象的方法列表数组中寻找对应的 selector,然后跳转执行
类方法的调用过程为:通过类对象的 isa 指针找到其所属的元类对象,在元类对象的方法列表数组中寻找对应的 selector,然后跳转执行举个例子:
// stringWithFormat: 消息被发送给了 NSString 的类对象,NSString 的类对象通过自己的 isa 指针找到 NSString 的元类对象 // 在 NSString 的元类对象的方法列表中找到对应的 stringWithFormat: 方法,然后执行该方法 NSString* str = [NSString stringWithFormat:@"%@ = %d", @"result", 3]; -
实例对象、类对象、元类对象 之间的关系

关于isa指针:- 实例对象的
isa指针指向类对象,类对象的isa指针指向元类对象 - 所有元类对象的
isa指针都直接指向NSObject的元类对象(因此,NSObject的元类对象也被称为根元类) - 根元类的
isa指针指向自己
关于
superclass指针:- 实例对象没有
superclass指针 - 类对象的
superclass指针指向(父类的类对象),(父类的类对象)的superclass指针指向(根类的类对象),(根类的类对象)的superclass指针指向nil - 元类对象的
superclass指针指向(父类的元类对象),(父类的元类对象)的superclass指针指向(根类的元类对象) - (根类的元类对象)的
superclass指针指向(根类的类对象),(根类的类对象)的superclass指针最终指向nil
注意:
- 同一个类的所有实例对象,有且仅有一个与之对应的类对象
- 每个类对象有且仅有一个与之对应的元类对象
- 每一个类对象有且仅有一个父类对象
- 每一个元类对象有且仅有一个父元类对象
- 实例对象的
-
实例对象的内存布局
Person类继承自NSObject,Man类继承自Person类,Person类和Man类各自有成员变量和属性,如下所示:


使用 XCode 命令行工具,将 Man.m 转换成 Man.cpp:// 如果转换失败,请检查:XCode - Preferences... - Locations - Command Line Tool 选项是否正确地选择了当前的 XCode 版本 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Man.m -o Man.cpp在 Man.cpp 中得到表示 NSObject 实例对象、Person 实例对象、Man 实例对象 的结构体,如下所示:
// RunTime 中表示 NSObject 实例对象的结构体 struct NSObject_IMPL { Class isa; }; // RunTime 中表示 Person 实例对象的结构体 struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int personA; NSString *personB; }; // RunTime 中表示 Man 实例对象的结构体 struct Man_IMPL { struct Person_IMPL Person_IVARS; int manA; NSString *manB; int _manC; NSString * _Nonnull _manD; }; // Man 实例对象的内存布局等价于 struct Man_IMPL { Class isa; // 继承自 NSObject 类的成员变量 int personA; // 继承自 Person 类的成员变量 NSString *personB; // 继承自 Person 类的成员变量 int manA; // Man 类自身的成员变量 NSString *manB; // Man 类自身的成员变量 int _manC; // Man 类自身的成员变量 NSString * _Nonnull _manD; // Man 类自身的成员变量 }; // 注意: // 1.在 Objective-C 中,实例对象的本质是一个结构体 // 2.实例对象结构体的第一个元素恒定为 isa 指针,指向其对应的类对象 // 3.实例对象结构体中包含继承自父类的成员变量(@public、@protected) // 4.实例对象结构体中包含其自身所有的成员变量(@public、@protected、@private、@package)
成员变量 && 属性 && 方法 && 协议
-
Ivar(成员变量)
Ivar(成员变量)被定义为指向
struct objc_ivar的指针,其数据结构如下:// An opaque type that represents an instance variable.(一个不透明的类型,用于表示一个成员变量) typedef struct objc_ivar *Ivar; // 用于描述实例对象的单个成员变量 struct objc_ivar { char * _Nullable ivar_name; // 成员变量名称 char * _Nullable ivar_type; // 成员变量类型(并不是真实的成员变量类型,而是经过类型编码的 C 字符串) int ivar_offset; // 成员变量相对于基地址的偏移量(Byte,基地址指的是实例对象结构体的首地址) #ifdef __LP64__ int space; #endif }; // 用于描述实例对象所具有的成员变量列表 struct objc_ivar_list { int ivar_cou

本文深入探讨Objective-C的Runtime机制,包括其实现原理、对象结构、成员变量与方法的存储方式,以及消息传递机制等内容。
最低0.47元/天 解锁文章
5352

被折叠的 条评论
为什么被折叠?



