(三)、objc_msgSend执行流程
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段:
- 消息发送
- 动态方法解析
- 消息转发
阅读源码:objc_msgSend的源码是用汇编实现的:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
/*x0寄存器:消息接收者,receiver*/
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
- 源码执行流程
- bjc-msg-arm64.s 文件 (汇编)
(1)ENTRY _objc_msgSend (2)b.le LNilOrTagged (3)CacheLookup NORMAL (4).macro CacheLookup (5).macro CheckMiss (6)STATIC_ENTRY __objc_msgSend_uncached (7).macro MethodTableLookup (8) __class_lookupMethodAndLoadCache3
- objc-runtime-new.mm
(1)_class_lookupMethodAndLoadCache3 (2)lookUpImpOrForward (3)getMethodNoSuper_nolock、search_method_list、log_and_fill_cache (4)cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache (5)_class_resolveInstanceMethod (6)_objc_msgForward_impcache
-objc-msg-arm64.s
(1) STATIC_ENTRY __objc_msgForward_impcache (2) ENTRY __objc_msgForward
- Core Foundation
__forwarding__(不开源)
(1)、objc_msgSend执行流程01-消息发送
(2)objc_msgSend执行流程02-动态方法解析
(3)objc_msgSend的执行流程03-消息转发(自己没有能力处理的时候,将消息转发给别人)
(四)、面试题
(1)、讲一下OC的消息机制
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给
receiver(方法调用者)发送了一条消息(selector方法名)- objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
(2)、消息转发机制流程
(3)、什么是Runtime?平时项目中有用过么?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。
- OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
- 平时编写的OC代码,底层都是转换成了Runtime API进行调用
- 具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
(四)、具体应用
(1)、动态方法交换:Method Swizzling
实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景,其原理是:通过Runtime获取到方法实现的地址(IMP),并且会清空方法缓存,进而动态交换两个方法的功能。使用到关键方法如下:
//获取类方法的Method
(1)Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
//获取实例对象方法的Method
(2)Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
//交换两个方法的实现
(3)void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
-
交换两个方法的实现
创建person类:HYPerson
HYPerson.h
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
- (void)run;
- (void)test;
@end
HYPerson.m
#import "HYPerson.h"
@implementation HYPerson
- (void)run
{
NSLog(@"%s", __func__);
}
- (void)test
{
NSLog(@"%s", __func__);
}
@end
main.m
#import <Foundation/Foundation.h>
#import "HYPerson.h"
#import <objc/runtime.h>
void exchangeMethod(){
//交换方法实现
HYPerson *person = [[HYPerson alloc] init];
Method runMethod = class_getInstanceMethod([HYPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([HYPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
[person test];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
exchangeMethod();
}
return 0;
}
打印结果:
2020-01-19 10:57:11.221840+0800 runtime-API-方法[21956:117139] -[HYPerson test]
2020-01-19 10:57:11.222216+0800 runtime-API-方法[21956:117139] -[HYPerson run]
Program ended with exit code: 0
-
拦截并替换系统方法:拦截button的点击事件,并统一处理
创建UIControl的分类:UIControl+Extension
-------UIControl+Extension.h-------
#import <UIKit/UIKit.h>
@interface UIControl (Extension)
@end
-------UIControl+Extension.m--------
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
+(void)load{
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(hy_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2); //交换的是IMP(方法的内存地址),并且会清空方法缓存(cache)
}
- (void)hy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// NSLog(@"%@--%@--%@",self,target,event);
if ([self isKindOfClass:[UIButton class]]) {
//拦截了所有按钮的点击事件
[self hy_sendAction:action to:target forEvent:event];//不覆盖系统的方法,依然实现系统的处理逻辑。
//(看着像是死循环,其实不然,因为已经交换了方法,[self hy_sendAction:action to:target forEvent:event]内部实现是- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event )
}else{
}
}
@end
(2)、实现分类添加属性:
我们开发中常常使用分类Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法,而且相比类目更是具有增加属性的优势,但是继承毕竟是一个重量级的操作,添加不必要的继承关系无疑增加了代码的复杂度。
遗憾的是,OC的类目并不支持直接添加属性,如果我们直接在分类的声明中写入Property属性,那么只能为其生成set与get方法声明,却不能生成成员变量,直接调用这些属性还会造成崩溃。
所以为了实现给分类添加属性,我们还需借助Runtime的关联对象(Associated Objects)特性,它能够帮助我们在运行阶段将任意的属性关联到一个对象上;下面是相关的三个方法:
/**
1.给对象设置关联属性
@param object 需要设置关联属性的对象,即给哪个对象关联属性
@param key 关联属性对应的key,可通过key获取这个属性,
@param value 给关联属性设置的值
@param policy 关联属性的存储策略(对应Property属性中的assign,copy,retain等)
OBJC_ASSOCIATION_ASSIGN @property(assign)。
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic)。
OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN @property(strong,atomic)。
OBJC_ASSOCIATION_COPY @property(copy, atomic)。
*/
void objc_setAssociatedObject(id _Nonnull object,
const void * _Nonnull key,
id _Nullable value,
objc_AssociationPolicy policy)
/**
2.通过key获取关联的属性
@param object 从哪个对象中获取关联属性
@param key 关联属性对应的key
@return 返回关联属性的值
*/
id _Nullable objc_getAssociatedObject(id _Nonnull object,
const void * _Nonnull key)
/**
3.移除对象所关联的属性
@param object 移除某个对象的所有关联属性
*/
void objc_removeAssociatedObjects(id _Nonnull object)
注意:key与关联属性一一对应,我们必须确保其全局唯一性,常用我们使用@selector(methodName)作为key。
创建一个UIImage的分类:UIImage+Tools
UIImage+Tools.h
#import <UIKit/UIKit.h>
@interface UIImage (Tools)
//添加一个新属性:图片网络链接
@property(nonatomic,copy)NSString *urlString;
- (void)clearAssociatedObjcet;
@end
UIImage+Tools.m
#import "UIImage+Tools.h"
#import <objc/runtime.h>
/*
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //关联对象的属性是弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //关联对象的属性是强引用并且关联对象不使用原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //关联对象的属性是copy并且关联对象不使用原子性
OBJC_ASSOCIATION_RETAIN = 01401, //关联对象的属性是copy并且关联对象使用原子性
OBJC_ASSOCIATION_COPY = 01403 //关联对象的属性是copy并且关联对象使用原子性
};
*/
@implementation UIImage (Tools)
- (void)setUrlString:(NSString *)urlString {
objc_setAssociatedObject(self, @selector(urlString), urlString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)urlString {
return objc_getAssociatedObject(self, @selector(urlString));
}
//添加一个自定义方法,用于清除所有关联属性
- (void)clearAssociatedObjcet{
objc_removeAssociatedObjects(self);
}
@end
ViewController.m
#import "ViewController.h"
#include "UIImage+Tools.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIImage *image = [[UIImage alloc]init];
image.urlString = @"图片";
NSLog(@"%@",image.urlString);
//清除关联的属性
[image clearAssociatedObjcet];
NSLog(@"%@",image.urlString);
}
打印结果:
2020-01-19 15:08:05.847526+0800 给分类添加属性[37968:191750] https://www.1244124-7d013e9e252dba2a.png
2020-01-19 15:08:05.847687+0800 给分类添加属性[37968:191750] 清除关联后:(null)
(3)、获取类的详细信息:
- 获取属性列表
代码实现
------HYPerson.h------
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
@property (assign, nonatomic) int ID;
@property (assign, nonatomic) int weight;
@property (nonatomic, assign) int age;
@property (copy, nonatomic) NSString *name;
@end
------main.m------
void propertyTest(){
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([HYPerson class], &count);
for (unsigned int i = 0; i<count; i++) {
const char *propertyName = property_getName(propertyList[I]);
NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
propertyTest();
}
return 0;
}
打印结果:
2020-01-19 11:19:57.530384+0800 runtime-API-类[26215:142464] PropertyName(0): ID
2020-01-19 11:19:57.530727+0800 runtime-API-类[26215:142464] PropertyName(1): weight
2020-01-19 11:19:57.530773+0800 runtime-API-类[26215:142464] PropertyName(2): age
2020-01-19 11:19:57.530810+0800 runtime-API-类[26215:142464] PropertyName(3): name
Program ended with exit code: 0
- 获取所有成员变量
------HYPerson.h------
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject{
int height;
NSString *sex;
}
@property (nonatomic, assign) int age;
@property (copy, nonatomic) NSString *name;
@end
------main.m------
void ivarTest(){
//成员变量
unsigned int count;
Ivar *ivars = class_copyIvarList([HYPerson class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[I];
NSLog(@"Ivar(%d): %s type:%s", i,ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
ivarTest();
}
return 0;
}
打印结果:
2020-01-19 11:27:24.061335+0800 runtime-API-类[27683:154887] Ivar(0): height type:i
2020-01-19 11:27:24.061911+0800 runtime-API-类[27683:154887] Ivar(1): name type:@"NSString"
2020-01-19 11:27:24.061978+0800 runtime-API-类[27683:154887] Ivar(2): _age type:i
2020-01-19 11:27:24.062018+0800 runtime-API-类[27683:154887] Ivar(3): _name type:@"NSString"
Program ended with exit code: 0
- 获取所有方法
------HYPerson.h------
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
@property (copy, nonatomic) NSString *name;
- (void)run;
@end
------main.m------
void methodTest(){
unsigned int count;
Method *methodList = class_copyMethodList([HYPerson class], &count);
for (unsigned int i = 0; i<count; i++) {
Method method = methodList[I];
SEL mthodName = method_getName(method);
NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName));
}
free(methodList);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
methodTest();
}
return 0;
}
打印结果: .cxx_destruct
- 当注释掉所有的成员变量的时候就不在调用了(其中也包括属性)就不会再调用这个方法了。
- 后来了解到这个方法就是在ARC模式下,将所有的成员变量变成nil相当于MRC模式下的dealloc。
2020-01-19 11:32:57.717172+0800 runtime-API-类[28744:162966] MethodName(0): .cxx_destruct
2020-01-19 11:32:57.717580+0800 runtime-API-类[28744:162966] MethodName(1): name
2020-01-19 11:32:57.717635+0800 runtime-API-类[28744:162966] MethodName(2): setName:
2020-01-19 11:32:57.717681+0800 runtime-API-类[28744:162966] MethodName(3): run
Program ended with exit code: 0
- 获取当前遵循的所有协议
void protocolTest(){
unsigned int count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([HYPerson class], &count);
for (int i=0; i<count; i++) {
Protocol *protocal = protocolList[I];
const char *protocolName = protocol_getName(protocal);
NSLog(@"protocol(%d): %@",i, [NSString stringWithUTF8String:protocolName]);
}
free(protocolList);
}
注意:C语言中使用Copy操作的方法,要注意释放指针,防止内存泄漏。
(4)、解决同一方法高频率调用的效率问题:
Runtime源码中的IMP作为函数指针,指向方法的实现。通过它,我们可以绕开发送消息的过程来提高函数调用的效率。当我们需要持续大量重复调用某个方法的时候,会十分有用,具体代码示例如下:
- (void)viewDidLoad {
[super viewDidLoad];
IMP imp = [self methodForSelector:@selector(sayHi)];
imp();
}
- (void)sayHi {
NSLog(@"hello world");
}
OC调用方法, 编译后就走objc_msgSend这套逻辑去了, 最终目的就是找到方法对应的函数指针(IMP), 再调用函数指针. 所以你找到IMP之后保存起来, 下次连续执行N次都可以直接调用IMP, 不需要走消息查找逻辑了, 效率自然就提高了。
(5)、方法动态解析与消息转发:
我们在分析了OC语言对应的底层C结构之后,现在可以进一步理解运行时的消息发送机制。先前讲到,OC调用方法被编译转化为如下的形式:
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
其实,除了常见的objc_msgSend,消息发送的方法还有objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret等,如果消息传递给父类就使用带有super的方法,如果返回值是结构体而不是简单值就使用带有stret的值。
运行时阶段的消息发送的详细步骤如下:
- 检测selector 是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain,release 这些函数了。
- 检测target 是不是nil 对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会 Crash,因为会被忽略掉。
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。
- 如果在cache里找不到就找一下方法列表methodLists。
- 如果methodLists找不到,就到父类的方法列表里寻找,一直找,直到找到NSObject类为止。
-
如果还找不到,Runtime就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向,这三种方法的调用关系如下图:
-
动态方法解析(Dynamic Method Resolution)
所谓动态解析,我们可以理解为通过cache和方法列表没有找到方法时,Runtime为我们提供一次动态添加方法实现的机会,主要用到的方法如下:
//OC方法:
//类方法未找到时调起,可于此添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel
//实例方法未找到时调起,可于此添加实例方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
//Runtime方法:
/**
运行时方法:向指定类中添加特定方法实现的操作
@param cls 被添加方法的类
@param name selector方法名
@param imp 指向实现方法的函数指针
@param types imp函数实现的返回值与参数类型
@return 添加方法是否成功
*/
BOOL class_addMethod(Class _Nullable cls,
SEL _Nonnull name,
IMP _Nonnull imp,
const char * _Nullable types)
下面使用一个示例来说明动态解析:Perosn类中声明方法却未添加实现,我们通过Runtime动态方法解析的操作为其他添加方法实现,具体代码如下:
HYPerson.h
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
//声明实例方法,但未实现
- (void)test;
//声明类方法,但未实现
+ (void)testTwo;
@end
HYPerson.m
#import "HYPerson.h"
#import <objc/runtime.h>
@implementation HYPerson
//重写父类方法:处理实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
//获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// NSLog(@"%s,%s,%p",sel,method_getTypeEncoding(method),method_getImplementation(method));
//动态添加test方法的实现
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
//重写父类方法:处理类方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(testTwo)) {
Method method = class_getClassMethod(object_getClass(self), @selector(otherClass));
class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)other {
NSLog(@"%s",__func__);
}
+ (void)otherClass {
NSLog(@"%s",__func__);
}
@end
调用:
#import <Foundation/Foundation.h>
#import "HYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
HYPerson *person = [[HYPerson alloc]init];
[person test];
[HYPerson testTwo];
}
return 0;
}
打印结果:
2020-01-19 16:36:10.115959+0800 objc_msgSend-动态解析[53652:288019] -[HYPerson other]
2020-01-19 16:36:10.116560+0800 objc_msgSend-动态解析[53652:288019] +[HYPerson otherClass]
Program ended with exit code: 0
注意:成功使用动态方法解析还有个前提,那就是我们必须存在可以处理消息的方法,比如上述代码中的other:与otherClass:
- 消息接收者(receiver)重定向:
我们注意到动态方法解析过程中的两个resolve方法都返回了布尔值,当它们返回YES时方法即可正常执行,但是若它们返回NO,消息发送机制就进入了消息转发(Forwarding)的阶段了,我们可以使用Runtime通过下面的方法替换消息接收者的为其他对象,从而保证程序的继续执行。
//重定向类方法的消息接收者,返回一个类
+ (id)forwardingTargetForSelector:(SEL)aSelector
//重定向实例方法的消息接受者,返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector
1)实例方法
创建HYPerson、HYCat两个类,无继承关系。
HYPerson:
HYPerson.h
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
- (void)test;
@end
-----------------------------------------------
HYPerson.m
#import "HYPerson.h"
#import "HYCat.h"
@implementation HYPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[HYCat alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
HYCat:
HYCat.h
#import <Foundation/Foundation.h>
@interface HYCat : NSObject
- (void)test;
@end
-----------------------------------------------
HYCat.m
#import "HYCat.h"
@implementation HYCat
- (void)test{
NSLog(@"%s",__func__);
}
@end
调用:
#import <Foundation/Foundation.h>
#import "HYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
HYPerson *person = [[HYPerson alloc]init];
[person test];
}
return 0;
}
打印结果:
2020-01-19 17:00:45.258329+0800 objc_msgSend-消息转发[58056:316915] -[HYCat test]
Program ended with exit code: 0
2)类方法
创建HYPerson、HYCat两个类,无继承关系。
HYPerson:
HYPerson.h
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
+ (void)test;
@end
-----------------------------------------------
HYPerson.m
#import "HYPerson.h"
#import "HYCat.h"
@implementation HYPerson
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [HYCat class];
}
return [super forwardingTargetForSelector:aSelector];
}
HYCat:
#import <Foundation/Foundation.h>
@interface HYCat : NSObject
+ (void)test;
@end
-----------------------------------------------
#import "HYCat.h"
@implementation HYCat
+ (void)test{
NSLog(@"%s",__func__);
}
@end
调用:
#import <Foundation/Foundation.h>
#import "HYPerson.h"
//元类对象是一种特殊的类对象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[HYPerson test];
}
return 0;
}
打印结果:
2020-01-19 17:04:12.960968+0800 objc_msgSend-消息转发[58716:322267] +[HYCat test]
Program ended with exit code: 0
注意:动态方法解析阶段返回NO时,我们可以通过forwardingTargetForSelector可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是非nil,非self,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。
-
消息重定向:
当以上两种方法无法生效,那么这个对象会因为找不到相应的方法实现而无法响应消息,此时Runtime系统会通过forwardInvocation:消息通知该对象,给予此次消息发送最后一次寻找IMP的机会:
- (void)forwardInvocation:(NSInvocation *)anInvocation;
其实每个对象都从NSObject类中继承了forwardInvocation:方法,但是NSObject中的这个方法只是简单的调用了doesNotRecongnizeSelector:方法,提示我们错误。所以我们可以重写这个方法:对不能处理的消息做一些默认处理,也可以将消息转发给其他对象来处理,而不抛出错误。
我们注意到anInvocation是forwardInvocation唯一参数,它封装了原始的消息和消息参数。正是因为它,我们还不得不重写另一个函数:methodSignatureForSelector。这是因为在forwardInvocation: 消息发送前,Runtime系统会向对象发送methodSignatureForSelector消息,并取到返回的方法签名用于生成NSInvocation对象。
1)实例方法:
HYPerson类:
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
- (int)test:(int)age;
@end
---------------------------------------------------------
#import "HYPerson.h"
#import "HYCat.h"
@implementation HYPerson
//方法签名:返回值类型,参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test:)) {
// return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 参数顺序:receiver、selector、other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[MJCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[MJCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[HYCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
@end
注意:我们注意到[NSMethodSignature signatureWithObjCTypes:]方法中的特殊参数“v@”,具体可参考这里
HYCat类:
#import <Foundation/Foundation.h>
@interface HYCat : NSObject
- (int)test:(int)age;
@end
---------------------------------------------------------
#import "HYCat.h"
@implementation HYCat
- (int)test:(int)age
{
return age * 2;
}
@end
调用:
#import <Foundation/Foundation.h>
#import "HYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
HYPerson *person = [[HYPerson alloc]init];
[person test:15];
}
return 0;
}
打印结果:
2020-01-19 17:18:33.841385+0800 objc_msgSend-消息转发[61249:336859] 30
Program ended with exit code: 0
2)实例方法:
HYPerson类:
#import <Foundation/Foundation.h>
@interface HYPerson : NSObject
+ (int)test:(int)age;
@end
---------------------------------------------------------
#import "HYPerson.h"
#import "HYCat.h"
@implementation HYPerson
//方法签名:返回值类型,参数类型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test:)) {
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[HYCat class]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
@end
注意:我们注意到[NSMethodSignature signatureWithObjCTypes:]方法中的特殊参数“v@”,具体可参考这里
HYCat类:
#import <Foundation/Foundation.h>
@interface HYCat : NSObject
+ (int)test:(int)age;
@end
---------------------------------------------------------
#import "HYCat.h"
@implementation HYCat
+ (int)test:(int)age
{
return age * 2;
}
@end
调用:
#import <Foundation/Foundation.h>
#import "HYPerson.h"
//元类对象是一种特殊的类对象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[HYPerson test:15];
}
return 0;
}
打印结果:
2020-01-19 17:18:33.841385+0800 objc_msgSend-消息转发[61249:336859] 30
Program ended with exit code: 0