Runtime运行时之消息转发

消息转发


Person *person = [[Person alloc]init];

[person performSelector:@selector(say:)];

当我们实例化person对象时,通过performSelector调用say方法时,Person没有实现这个方法,默认情况下,对象接收到未知的消息,会导致程序崩溃,通过控制台,我们可以看到以下异常信息

-[Person say:]: unrecognized selector sent to instance 0x61000000c540


下面通过消息转发机制来处理这些异常信息

动态方法解析

void dyMethod(id self,SEL _cmd,NSString *str)

{

NSLog(@"%@",str);

}

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

NSString *selectorString = NSStringFromSelector(sel);

if ([selectorString isEqualToString:@"say:"])

{

class_addMethod(self.class, sel, (IMP)dyMethod, "v@:@");

}

return [super resolveInstanceMethod:sel];

}

我们在Person中重写上面方法去实现动态添加方法,就可以了。

下面介绍一下class_addMethod的使用。一下来自苹果官方文档翻译

class_addMethod 使用

定义

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

参数

cls :  添加到方法中的类.

name: 一个选择器,用于指定方法的名称.

imp:一个函数的新方法的实现。函数必须至少两个参数self和_cmd.

类型

types:描述的字符数组的类型参数的方法。可能的值,请参阅objective - c运行时编程指南>类型编码。自函数必须至少两个参数self 和_cmd,第二个和第三个字符必须“@:”(第一个字符是返回类型).

返回值

如果方法添加成功就返回YES,否则返回NO(例如,这个方法已经包含这个方法名字的实现了).

探讨

class_addMethod将添加一个覆盖父类的实现,但不会取代现有的实现类。改变现有的实现中,使用method_setimplementation。一个objective - C方法仅仅是一个C函数,至少需要两个参数self和_cmd。例如,给出以下函数:

列表1

void myMethodIMP(id self, SEL _cmd)

{

// implementation ....

}

你可以像这样动态的把一个方法添加到一个类中

列表 2

class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP) myMethodIMP, "v@:");


以下是上面例子中 "v@:@" 写法的对应表格,v代表void 无返回值,@代表self  :代表sel  @代表nsstring

Code      Meaning

c             A char(一个char类型)

i             An int

s            A short

l            A longl is treated as a 32-bit quantity on 64-bit programs.

q          A long long

C         An unsigned char

I            An unsigned int

S          An unsigned short

L           An unsigned long

Q          An unsigned long long

f          A float

d          A double

B          A C++ bool or a C99 _Bool

v         A void

*         A character string (char *)

@        An object (whether statically typed or typed id)

#       A class object (Class)

:         A method selector (SEL)

[array type]            An array

{name=type...}      A structure

(name=type...)       A union

bnum                     A bit field of num bits

^type                     A pointer to type

?      An unknown type (among other things, this code is used for function pointers)

备用接收者

如果在上一步无法处理消息,则Runtime会继续调以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

上代码:

- (void)viewDidLoad {

[super viewDidLoad];

//    Person *person = [[Person alloc]init];

//    [person performSelector:@selector(say:) withObject:@"come from person"];

[self performSelector:@selector(say:) withObject:@"come from person"];

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

NSLog(@"forwardingTargetForSelector");

NSString *selectorString = NSStringFromSelector(aSelector);

Person *person = [[Person alloc]init];

// 将消息转发给person来处理

if ([selectorString isEqualToString:@"say:"]) {

return person;

}

return [super forwardingTargetForSelector:aSelector];

}

我们这里ViewController 也调用say:方法,我们通过重写forwardingTargetForSelector,如果self无法处理消息,那么我们让person来处理此消息。

2017-09-05 17:43:16.350 消息转发[63281:33199162] forwardingTargetForSelector

2017-09-05 17:43:30.449 消息转发[63281:33199162] come from person

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

上代码:

- (void)viewDidLoad {

[super viewDidLoad];

_person = [[Person alloc]init];

[self performSelector:@selector(say:) withObject:@"come from person"];

}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (!signature) {

if ([Person instancesRespondToSelector:aSelector]) {

signature = [Person instanceMethodSignatureForSelector:aSelector];

}

}

return signature;

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

if ([Person instancesRespondToSelector:anInvocation.selector]) {

[anInvocation invokeWithTarget:_person];

}

}

这样person就会处理消息。

完整demo地址

你可能感兴趣的:(Runtime运行时之消息转发)