消息转发
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地址