首先要知道Runtime的时候类的结构:
struct objc_class {
Class_Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char *_Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
通过之前的博客我们知道了,iOS 的方法调用,在运行时的时候会是给某个对象发消息,然后在这个类的MethodList里取寻找有没有调用的这个方法。
那么问题来了,如果我们给一个对象发送消息的时候(即调用该对象的方法),这个方法没在这个对象的MethodList中找到,那么会怎么样?
想必大家都已经知道结果了,那就是遇到我们最熟悉的Crash。
unrecognized selector sent to instance
这时候这个被调用的哥们可能会骂你,你TM有病啊,你自己没有给我定义这个方法你TM还给我发消息。
没办法,哥们,我真的需要你来给我做这件事。你可以找你的兄弟去帮忙,然后给我结果就好了。
然后这哥们就告诉了你,他找不到方法的时候,应该怎么能拿到结果。
resolveInstanceMethod:
方法 (或 resolveClassMethod:
)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。
forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
。
forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
iOS如何消息转发
1.首先在类方法列表中没有找到方法,那么系统会调用resolveInstanceMethod或者resolveClassMethod,让你动态添加方法实现。
/**
* 通过这个方法来实现动态添加方法
*
* @param sel 没有实现的方法
*
* @return 返回YES处理方法或者NO转发到下一步
*/
+(BOOL)resolveInstanceMethod:(SEL)sel{
//方法名
NSString *selStr = NSStringFromSelector(sel);
if ([selStr isEqualToString:@"XXXX1"]) {
//增加你要实现的方法
class_addMethod(self, sel, (IMP)AAAA, "@@:");
return YES;
}
if ([selStr isEqualToString:@"XXXX2:"]) {
class_addMethod(self, sel, (IMP)BBBB, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
/**
* 这个方法实现XXXX2的转发
*
* @param self 对象
* @param cmd 方法
* @param value 传入的值
*/
void BBBB(idself, SEL cmd,id value){
}
/**
* 这个方法用于XXXX1的转发
*
* @param self 对象自己
* @param cmd 方法名
*
* @return 返回得到的值
*/
id AAAA(idself, SEL cmd){
}
顺便说一下: class_addMethod方法的使用
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
参数说明:
cls:被添加方法的类
name:可以理解为方法名,这个貌似随便起名,比如我们这里叫sayHello2
imp:实现这个方法的函数
types:一个定义该函数返回值类型和参数类型的字符串,
types具体符号讲解:
例如 AAAA的参数 @@:
按顺序分别表示:
第一个参数@ 表示返回值为id
返回类型int 用 i 表示 , 返回void用v表示
第二个参数@ 表示参数self
还可以表示OC类型的参数
第三个参数: 表示SEL(_cmd)
2.如果第一个方法返回NO,转发进入下一步forwardingTargetForSelector
/**
* 转发到另一个对象去处理,其他的下一步
*
* @param aSelector 方法
*
* @return 返回转发的处理对象或者nil
*/
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSString *selStr =NSStringFromSelector(aSelector);
//如果是没有实现的方法,则处理转发
if ([selStrisEqualToString:@"Method1"]) {
//返回处理这个转发的对象
return MethodModel;
}else{
return [superforwardingTargetForSelector:aSelector];
}
}
3.如果没有转发对象,上一步返回未nil,则进行下一步转发。如果返回nil,doesNotRecognizeSelector报异常。
/**
* 是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
*
* @param aSelector 方法名
*
* @return 返回一个签名
*/
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *sig =nil;
NSString *selStr = NSStringFromSelector(aSelector);
//判断你要转发的SEL
if ([selStr isEqualToString:@"Method2"]) {
//此处返回的sig是方法forwardInvocation的参数anInvocation中的methodSignature
//为你的转发方法手动生成签名
sig = [Method2Model methodSignatureForSelector:@selector(Method2)];
}else{
sig = [super methodSignatureForSelector:aSelector];
}
return sig;
}
/**
* 转发方法打包转发出去
*
* @param anInvocation
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"Method2"]) {
//设置处理转发的对象
[anInvocation setTarget:self.companyModel];
//设置转发对象要用的方法
[anInvocation setSelector:@selector(Method2:)];
BOOL Argument =YES;
//第一个和第二个参数是target和sel
[anInvocation setArgument:&Argument atIndex:2];
[anInvocation retainArguments];
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
}
Demo地址:https://github.com/RainManGO/Runtime-Message-Forwarding