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_count; // 成员变量的总个数 #ifdef __LP64__ int space; #endif /* variable length structure(可变长度的结构体数组) */ struct objc_ivar ivar_list[1]; } -
Property(属性)
Property(属性)被定义为指向
struct objc_property的指针,其数据结构如下:// An opaque type that represents an Objective-C declared property.(一个不透明的类型,用于表示一个属性) typedef struct objc_property *objc_property_t; // 用于描述实例对象的单个属性(RunTime 源码中未找到,根据 -rewrite-objc 后的 c++ 文件所推测) struct objc_property { char * _Nullable property_name; // 属性名称 char * _Nullable property_attributes; // 属性特性 }; // 用于描述实例对象所具有的属性列表(RunTime 源码中未找到,根据 -rewrite-objc 后的 c++ 文件所推测) struct property_list_t { int entsize; // 单个属性的大小,sizeof(struct objc_property) int property_count; // 属性的总个数 /* variable length structure(可变长度的结构体数组) */ struct objc_property property_list[1]; }其中,
objc_property.property_attributes是一串用于表示属性特性的字符串编码// 特性编码总以 T属性类型编码 开头,总以 V属性所对应的成员变量 结尾,中间是属性修饰符编码,所有的编码之间以逗号隔开(T-Type, V-Value) // 举例如下: // 1.属性 name 所对应的特性编码为 T@"NSString",&,N,V_name @property (nonatomic, strong) NSString* name; // 2.属性 age 所对应的特性编码为 Ti,V_age @property (atomic, assign) int age; // 3.属性 survival 所对应的特性编码为 TB,R,N,GisSurvival,V_survival @property (nonatomic, assign ,getter=isSurvival, readonly) bool survival; // 用于描述属性的单个特性 typedef struct { const char * _Nonnull name; // 特性名称 const char * _Nonnull value; // 特性值(通常为空) } objc_property_attribute_t;属性修饰符编码: ① 原子性 atomic 默认值,空 nonatomic N ② 读写权限 readwrite 默认值,空 readonly R getter G方法名 setter S方法名(带冒号) ③ 内存管理 assign 默认值,空 weak W(弱引用) strong &(强引用) copy C属性类型编码: c 代表 char 类型 i 代表 int 类型 s 代表 short 类型 l 代表 long 类型(在 64 位的程序上也被视为一个 32 位的数据) q 代表 long long 类型 C 代表 unsigned char 类型 I 代表 unsigned int 类型 S 代表 unsigned short 类型 L 代表 unsigned long 类型 Q 代表 unsigned long long 类型 f 代表 float 类型 d 代表 double 类型 B 代表 C++ 中的 bool 或者 C99 中的 _Bool v 代表 void 类型 . * 代表 char * 类型 @ 代表实例对象 # 代表类对象(Class) : 代表方法 selector(SEL) [array type] 代表 array {name=type…} 代表结构体 (name=type…) 代表 union bnum 代表 num 个的 bit 位 ^type 代表指向 type 类型的指针 ? 代表一个未知的类型 (此外,此编码还用于表示函数指针) 关于类型编码更详细的介绍,请参考苹果开发者文档(https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) NSLog(@"%s", @encode(int)); // 输出 i NSLog(@"%s", @encode(unsigned int)); // 输出 I NSLog(@"%s", @encode(NSInteger)); // 输出 q NSLog(@"%s", @encode(NSString)); // 输出 {NSString=#} -
Method(方法)
Method(方法)被定义为指向
struct objc_method的指针,其数据结构如下:// An opaque type that represents a method in a class definition.(一个不透明的类型,用于表示 实例对象或者类对象 中的方法) typedef struct objc_method *Method; // 用于描述 实例对象或者类对象 的单个方法 struct objc_method { SEL _Nonnull method_name; // 方法名称 char * _Nullable method_types; // 方法类型(类型编码,用于存储方法的返回值类型和参数类型) IMP _Nonnull method_imp; // 方法实现(本质上是一个函数指针) }; // 用于描述 实例对象或者类对象 的方法列表 struct objc_method_list { struct objc_method_list * _Nullable obsolete; // 指向下一个方法链表(已废弃) int method_count; // 本方法列表中方法的总个数 #ifdef __LP64__ int space; #endif /* variable length structure(可变长度的结构体数组) */ struct objc_method method_list[1]; };① SEL 是
selector在 Objective-C 中的表示类型(Swift 中是Selector类)
selector是方法选择器(也叫:方法编号、方法名称),可以理解为用于区分方法的 ID,其数据结构如下:// An opaque type that represents a method selector.(一个不透明的类型,用于表示一个方法选择器) typedef struct objc_selector *SEL; // SEL 是一个指向 struct objc_selector 的指针,其本质是一个 int 类型的地址,地址中存放着方法的名字 // A method selector is a C string that has been registered (or "mapped") with the Objective-C runtime. (方法选择器是一个已在 Objecte-C 运行时中注册(或"映射")的C字符串) // Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.(由编译器生成的方法选择器在加载类时由运行时自动映射) selector 本质上是个映射到方法的 C 字符串(用于保存方法名称的 C 字符串) 可以用 Objective-C 编译器命令 @selector() 或者 RunTime 的 sel_registerName() 函数来手动将方法名映射到运行时以获得一个 SEL 类型的方法选择器在 Objective-C 中关于方法的名称有 2 条规则:
- 同一个类,
selector不能重复 - 不同的类,
selector可以重复
在 C 中可以通过不同的参数类型对函数进行重载(即函数名称和参数个数相同,但参数类型不同)
在 Objective-C 中,因为selector只存储了方法的名称,并没有存储方法的参数类型,所以没有办法通过selector区分具有相同方法名称、不同参数类型的方法。这也是 Objective-C 不支持方法重载的最根本的原因// 在 C 中,可以通过不同的参数类型对方法进行重载 int caculate(int num1, int num2); float caculate(float num1, float num2); // 在 Objective-C 中,无法通过不同的参数类型对方法进行重载(以下的两个方法会报重复定义的错误) -(int)caculateNum1:(int)num1 num2:(int)num2; -(float)caculateNum1:(float)num1 num2:(float)num2; // 如果要避免重复定义,只能通过不同的方法名进行区分 -(int)caculateIntNum1:(int)num1 num2:(int)num2; -(float)caculateFloatNum1:(float)num1 num2:(float)num2;还需要注意一点:
在不同的类中相同名字的方法所对应的selector是相同的,即使这些方法,名字相同而变量类型不同,也会导致它们具有相同的selector② 方法类型 用于存储方法的返回值类型和参数类型,请参考属性的类型编码
// 注意:类型编码后面的数字表示的是该类型的起始位置(字节) // v16@0:8 等价于 v@: -(void)test; // i24@0:8i16i20 等价于 i@:ii -(int)caculateIntNum1:(int)num1 num2:(int)num2; // f24@0:8f16f20 等价于 f@:ff -(float)caculateFloat:(float)num1 num2:(float)num2; // @36@0:8@16i24f28B32 等价于 @@:@ifB -(NSString *)introduceName:(NSString *)name age:(int)age height:(float)height sex:(bool)sex;③ IMP 本质上是一个函数指针,指向方法在内存中的代码实现,其数据结构如下:
// A pointer to the function of a method implementation. (一个指向方法实现的函数指针) // 注意:IMP 指向的函数 与 objc_msgSend 函数,类型相同。参数都包含:id、SEL、可变参数列表 #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif - 同一个类,
-
Cache(方法缓存)
Cache(方法缓存、类缓存)被定义为指向
struct objc_cache的指针,其数据结构如下:typedef struct objc_cache *Cache; struct objc_cache { unsigned int mask; // 当前能到达的最大 index(从 0 开始计算),缓存的长度 total = mask + 1 unsigned int occupied; // 被占用的槽位数量,因为缓存是以散列表的形式存在的,所以会有空槽 Method _Nullable buckets[1]; // 用数组表示的 hash 表,每一个 Method 代表一个方法缓存 };如果每次调用对象的方法时,都按照对象的继承链依次遍历方法列表,会导致方法查找效率低下。基于局部性原理:当对象的某个方法被调用时,该方法在以后很可能会再次被调用。为了加快方法调用的速度,RunTime 会将调用过的方法存放在
Cache中进行缓存,并且每当调用对象的方法时,都会优先从Cache中进行查找 -
Protocol(协议)
Protocol(协议)被定义为
struct protocol_t,其数据结构如下:// 用于表示 Objective-C 中的一个协议(继承自 struct objc_object) struct protocol_t : objc_object { const char *mangledName; // 协议名称(C++ 命名重整技术 name mangling) struct protocol_list_t *protocols; // 本协议所遵守的协议列表 method_list_t *instanceMethods; // 实例方法列表(必须实现) method_list_t *classMethods; // 类方法列表(必须实现) method_list_t *optionalInstanceMethods; // 实例方法列表(可选实现) method_list_t *optionalClassMethods; // 类方法列表(可选实现) property_list_t *instanceProperties; // 实例属性 uint32_t size; // sizeof(struct protocol_t) uint32_t flags; // 标志位 }; // 协议列表 struct objc_protocol_list { struct objc_protocol_list * _Nullable next; // 指向下一个协议链表 long count; // 本协议列表中协议的总个数 __unsafe_unretained Protocol * _Nullable list[1]; // 可变长度的结构体数组(协议列表) };
RunTime 消息机制
Objective-C 中,方法的调用都是类似 [receiver selector]; 的形式,其本质是让对象在运行时动态地发送消息。我们来看看 receiver 对象调用 selector 方法时,在『编译阶段』和『运行阶段』分别会发生什么:
-
编译阶段
[receiver selector];方法调用被编译器转换为:objc_msgSend(receiver, selector);(不带参数)objc_msgSend(recevier, selector, org1, org2, …);(带参数)
-
运行时:消息发送阶段
消息接受者
recevier寻找对应的selector:- 通过
recevier的isa指针找到recevier对应的Class(类) - 在
Class(类)的Cache(方法缓存)的散列表中寻找对应的IMP(方法实现) - 如果在
Cache(方法缓存)中没有找到对应的IMP(方法实现),则继续在Class(类)的Method List(方法列表)中找寻找对应的selector。如果找到,则填充到Cache(方法缓存)中,并返回selector - 如果在
Class(类)中没有找到这个selector,则继续在recevier的superclass(父类)中寻找 - 一旦找到对应的
selector,则直接执行selector对应的IMP(方法实现) - 如果找不到对应的
selector,则 RunTime 系统进入消息转发阶段
- 通过
-
运行时:消息转发阶段

① 动态方法解析:通过重写当前recevier的+resolveInstanceMethod:(对象方法)或者+resolveClassMethod:(类方法),利用 RunTime 的class_addMethod函数给当前recevier动态地添加其他方法实现② 消息接受者重定向:如果上一步中没有添加其他方法实现,则可重写当前
recevier的-forwardingTargetForSelector:(对象方法)或者+forwardingTargetForSelector:(类方法),将消息动态地转发给其他对象处理③ 完整的消息重定向:如果上一步的返回值为
nil或者self,则可重写当前recevier的-methodSignatureForSelector:(对象方法)或者+methodSignatureForSelector:(类方法),获取消息的参数和返回值类型- 如果
methodSignatureForSelector:返回了一个NSMethodSignature对象(方法签名),则 RunTime 系统就会创建一个NSInvocation对象,并调用当前recevier的-forwardInvocation:(对象方法)或者+forwardInvocation:(类方法),给予此次消息发送最后一次寻找IMP(方法实现)的机会 - 如果
recevier的methodSignatureForSelector:返回 nil,则 RunTime 系统发出-doesNotRecognizeSelector:(对象方法)或者+doesNotRecognizeSelector:(类方法)消息,程序也就崩溃了
- 如果
-
① 代码示例:动态方法解析
RunTime 会调用当前对象(
recevier)的+resolveInstanceMethod:或者+resolveClassMethod:,让程序有机会为未找到的方法提供一个函数实现。前者在(对象方法未找到时)调用,后者在(类方法未找到时)调用。可以通过重写当前对象(recevier)的这两个方法,添加其他函数实现,并返回 YES,那么 RunTime 就会重新启动一次消息发送的过程主要用到的方法如下:
// 对象方法未找到时调起,可以在此动态添加方法实现 +(BOOL)resolveInstanceMethod:(SEL)sel; // 类方法未找到时调起,可以在此动态添加方法实现 +(BOOL)resolveClassMethod:(SEL)sel; // 向指定的类中添加指定的方法 // param0.cls 被添加方法的类 // param1.name 方法的 selector // param2.imp 指向方法实现的函数指针 // param3.types 方法的类型 // return. 如果添加方法成功则返回 YES,否则返回 NO BOOL class_addMethod(Class cls, SEL name, IMP imp, const char * _Nullable types);代码示例:
#import "ViewController.h" #import <objc/runtime.h> @interface ViewController () @end @implementation ViewController -(void)viewDidLoad { [super viewDidLoad]; // 隐式调用 runMethod 方法 [self performSelector:@selector(runMethod)]; } // 1.重写该方法,添加对象方法的实现 +(BOOL)resolveInstanceMethod:(SEL)sel { // 如果是执行 runMethod 函数,就动态解析,指定新的 IMP if (sel == @selector(runMethod)) { class_addMethod([self class], sel, (IMP)runFunction, "v@:"); } return [super resolveInstanceMethod:sel]; } // runMethod 方法的函数实现 void runFunction(id recevier, SEL selector) { NSLog(@"%s", __func__); } @end输出结果:
2021-04-14 17:27:20.262071+0800 RunTimeDemo[3519:198578] runFunction从这个例子中,可以看出:
虽然ViewController.m没有实现runMethod方法,但是通过重写它的-resolveInstanceMethod:,利用class_addMethod函数动态添加对象方法的实现(runFunction函数),并执行。从打印结果来看,成功调起了runFunction函数 -
② 代码示例:消息接受者重定向
如果上一步中
+resolveInstanceMethod:或者+resolveClassMethod:没有添加其他函数实现,RunTime 就会进行下一步:消息接受者重定向
注意:无论+resolveInstanceMethod:或者+resolveClassMethod:是返回 YES,还是返回 NO,只要其中没有添加其他函数实现,RunTime 都会进行到这一步RunTime 会调用当前对象(
recevier)的-forwardingTargetForSelector:或者+forwardingTargetForSelector:方法,让程序有机会为未找到的方法提供一个备用接受者。前者用于(转发对象方法),后者用于(转发类方法)。可以通过重写当前对象(recevier)的这两个方法,返回一个备用接受者,那么 RunTime 就会将消息转发给备用接受者处理主要用到的方法如下:
// 重定向对象方法的消息接收者,返回一个类或实例对象 -(id)forwardingTargetForSelector:(SEL)aSelector; // 重定向类方法的消息接收者,返回一个类或实例对象 +(id)forwardingTargetForSelector:(SEL)aSelector;代码示例:
// Person.h #import <Foundation/Foundation.h> @interface Person : NSObject -(void)runMethod; @end // Person.m #import "Person.h" @implementation Person -(void)runMethod { NSLog(@"%s", __func__); } @end#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @end @implementation ViewController -(void)viewDidLoad { [super viewDidLoad]; // 隐式调用 runMethod 方法 [self performSelector:@selector(runMethod)]; } +(BOOL)resolveInstanceMethod:(SEL)sel { // 为了进行下一步:消息接收者重定向 return [super resolveInstanceMethod:sel]; } // 2.重写该方法,返回备用消息接收者 -(id)forwardingTargetForSelector:(SEL)aSelector { // 如果是执行 runMethod 方法,则返回 Person 对象作为备用消息接受者 if (aSelector == @selector(runMethod)) { Person* p = [[Person alloc] init]; return p; } return [super forwardingTargetForSelector:aSelector]; } @end输出结果:
2021-04-14 18:07:47.837306+0800 RunTimeDemo[3685:215156] -[Person runMethod]从这个例子中,可以看出:
虽然ViewController.m没有实现runMethod方法,+resolveInstanceMethod:也没有添加其他函数实现。但是通过重写-forwardingTargetForSelector:,返回备用接受者,把本应该ViewController处理的消息,转发给了Person对象去执行了。打印结果也证明成功实现了转发通过
-forwardingTargetForSelector:或者+forwardingTargetForSelector:可以重定向消息的接收者,该方法的返回值是一个对象,如果这个对象是不是nil,也不是self,则 RunTime 会将消息转发给这个对象执行。否则,继续进行下一步:完整的消息重定向 -
③ 代码示例:完整的消息重定向
如果经过动态方法解析、消息接受者重定向,RunTime 还是找不到相应的方法实现而无法响应消息,那么RunTime 就会调用当前对象(
recevier)的methodSignatureForSelector:方法,获取消息的参数和返回值类型:-
如果
methodSignatureForSelector:方法返回了一个NSMethodSignature对象(方法签名),那么 RunTime 就会创建一个NSInvocation对象(即把与消息相关的全部细节都封装到NSInvocation对象中),并调用当前对象(recevier)的forwardInvocation:方法,给予此次消息发送最后一次寻找IMP的机会 -
如果
methodSignatureForSelector:方法返回nil,那么 RunTime 就会调用当前对象(recevier)的doesNotRecognizeSelector:方法,程序也就崩溃了
所以可以通过重写前对象(
recevier)的methodSignatureForSelector:与forwardInvocation:,对消息进行转发主要用到的方法如下:
// 注意:对象方法和类方法消息转发第三步调用的方法同样不一样 // 对象方法调用的是: // 获取对象方法的参数和返回值类型,返回方法签名 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; // 对象方法消息重定向 -(void)forwardInvocation:(NSInvocation *)anInvocation // 报错:未识别的对象方法 -(void)doesNotRecognizeSelector:(SEL)aSelector; // 类方法调用的是: // 获取类方法的参数和返回值类型,返回方法签名 +(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; // 类方法消息重定向 +(void)forwardInvocation:(NSInvocation *)anInvocation; // 报错:未识别的类方法 +(void)doesNotRecognizeSelector:(SEL)aSelector;代码示例:
// Person.h #import <Foundation/Foundation.h> @interface Person : NSObject -(void)runMethod; @end // Person.m #import "Person.h" @implementation Person -(void)runMethod { NSLog(@"%s", __func__); } @end#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @end @implementation ViewController -(void)viewDidLoad { [super viewDidLoad]; // 隐式调用 runMethod 方法 [self performSelector:@selector(runMethod)]; } +(BOOL)resolveInstanceMethod:(SEL)sel { // 为了进行下一步:消息接收者重定向 return [super resolveInstanceMethod:sel]; } -(id)forwardingTargetForSelector:(SEL)aSelector { // 为了进行下一步:完整的消息重定向 return [super forwardingTargetForSelector:aSelector]; } // 3.1 重写该方法,获取消息的参数类型和返回值类型并返回方法签名 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([NSStringFromSelector(aSelector) isEqualToString:@"runMethod"]) { NSMethodSignature* methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return methodSignature; } return [super methodSignatureForSelector:aSelector]; } // 3.2 重写该方法进行完整的消息重定向 -(void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = anInvocation.selector; Person* p = [[Person alloc] init]; if ([p respondsToSelector:sel]) { [anInvocation invokeWithTarget:p]; return; } /* Dog* d = [[Dog alloc] init]; if ([d respondsToSelector:sel]) { [anInvocation invokeWithTarget:d]; return; } */ // ... [self doesNotRecognizeSelector:sel]; } // 3.3 报错:未识别的对象方法 -(void)doesNotRecognizeSelector:(SEL)aSelector { NSLog(@"对象方法未找到"); } @end输出结果:
2021-04-14 20:54:49.963125+0800 RunTimeDemo[3941:244153] -[Person runMethod]从这个例子中,可以看出:
虽然ViewController.m没有实现runMethod方法,+resolveInstanceMethod:没有添加其他函数实现,-forwardingTargetForSelector:也没有将消息转发给其他对象。但是 RunTime 通过调用-methodSignatureForSelector:获取了方法签名,进而创建了一个NSInvocation对象,并通过调用-forwardInvocation:把本应该ViewController处理的消息,转发给了Person对象去执行了。打印结果也证明成功实现了转发既然
-forwardingTargetForSelector:和-forwardInvocation:都可以将消息转发给其他对象处理,那么两者的区别在哪?两者区别就在于
-forwardingTargetForSelector:只能将消息转发给一个对象。而-forwardInvocation:可以将消息转发给多个对象。可以将
forwardInvocation:方法看成是,当前对象的(不能识别的消息的分发中心)。在forwardInvocation:方法中:-
可以将当前对象不能识别的消息,转发给其他对象
-
可以将当前对象不能识别的消息,翻译成其他消息
-
可以将当前对象不能识别的消息,简单地"吃掉"(这样,不能识别的消息既不会转发给其他对象,也不会报错)
-
本文深入探讨Objective-C的Runtime机制,包括其实现原理、对象结构、成员变量与方法的存储方式,以及消息传递机制等内容。
5496

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



