<简书 — 刘小壮> https://www.jianshu.com/p/f313e8e32946html
当一个对象的方法被调用时,首先在对象所属的类中查找方法列表,若是当前类中没有则向父类查找,一直找到根类NSObject
。若是始终没有找到方法实现,则进入消息转发步骤中。git
当一个方法没有实现时,也就是在cache lsit
和其继承关系的method list
中,没有找到对应的方法。这时会进入消息转发阶段,可是在进入消息转发阶段前,Runtime
会给一次机会动态添加方法实现。github
能够经过重写resolveInstanceMethod:
和resolveClassMethod:
方法,动态添加未实现的方法。其中第一个是添加实例方法,第二个是添加类方法。这两个方法都有一个BOOL
返回值,返回NO
则进入消息转发机制。app
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
复制代码
在经过class_addMethod
函数动态添加实现时,后面有一个"v@:"
来描述SEL
对应的函数实现,具体的描述能够参考官方文档。ide
在进行消息转发以前,还能够在forwardingTargetForSelector:
方法中将未实现的消息,转发给其余对象。能够在下面方法中,返回响应未实现方法的其余对象。函数
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorName = NSStringFromSelector(aSelector);
if ([selectorName isEqualToString:@"selector"]) {
return object;
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
当forwardingTargetForSelector:
方法未作出任何响应的话,会来到消息转发流程。消息转发时会首先调用methodSignatureForSelector:
方法,在方法内部生成NSMethodSignature
类型的方法签名对象。在生成签名对象时,能够指定target
和SEL
,能够将这两个参数换成其余参数,将消息转发给其余对象。布局
[otherObject methodSignatureForSelector:otherSelector];
复制代码
生成NSMethodSignature
签名对象后,就会调用forwardInvocation:
方法,这是消息转发中最后一步了,若是在这步尚未对消息进行处理,则会致使崩溃。学习
这个方法中会传入一个NSInvocation
对象,这个对象就是经过刚才生成的签名对象建立的,能够经过invocation
调用其余对象的方法,调用其invokeWithTarget:
便可。ui
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([object respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:object];
} else {
[super forwardInvocation:anInvocation];
}
}
复制代码
将一条消息发送给一个不能处理的对象会引发崩溃,可是在崩溃以前,系统给响应对象一次处理异常的机会。编码
当发送一条对象不能处理的消息,产生Crash
以前,系统会调用响应者的forwardInvocation:
方法,并传入一个NSInvocation
对象,NSInvocation
对象中包含原始消息及参数。这个方法只有方法未实现的时候才会调用。
你能够实现forwardInvocation:
方法,将消息转发给另外一个对象。forwardInvocation:
方法是一个动态方法,在响应者没法响应方法时,会调用forwardInvocation:
方法,能够重写这个方法实现消息转发。
消息转发中forwardInvocation:
须要作的是,确认消息将发送到哪里,以及用原始参数发送消息。能够经过invokeWithTarget:
方法,发送被转发的消息。调用invoke
方法后,原方法的返回值将被返回给调用方。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:someOtherObject];
} else {
[super forwardInvocation:anInvocation];
}
}
复制代码
forwardInvocation:
方法不仅能够处理一个方法,能够经过Selector
进行判断,来处理多个须要转发的方法。
在OC中有时候能够动态的提供方法实现,例如属性能够经过@dynamic propertyName;
的形式,表示将在运行过程当中动态的提供属性方法。
若是想实现动态方法解析,须要实现resolveInstanceMethod:
或resolveClassMethod:
方法,在这两个方法中动态的添加方法实现。经过class_addMethod
方法能够动态添加方法,添加方法时须要关联对应的函数指针,函数指针须要声明两个隐藏参数self
和_cmd
。
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
复制代码
消息转发和动态方法解析大部分是相同的,在消息转发以前一个类有机会动态解析这个方法。若是已经调用respondsToSelector:
或instancesRespondToSelector:
方法,动态方法解析有机会优先为Selector
添加IMP
。若是你实现了resolveInstanceMethod:
方法,但想要特定的Selector
走消息转发流程,则将此方法返回NO
便可。
能够经过消息转发机制来模拟多继承,例以下面这张图中,两个类中虽然不存在继承关系,可是却由另外一个类处理了Warrior
的事件。
由上面的例子能够看出,分属两个继承分支的类,经过消息转发机制实现了继承的关系。Warrior
的negotiate
消息由其“父类”Diplomat
来实现。
经过消息转发实现的多重继承相对于普通继承来讲更有优点,消息转发能够将消息转发给多个对象,这样就能够将代码按不一样职责封装为不一样对象,并经过消息转发给不一样对象处理。
须要注意的是,Warrior
虽然经过消息转发机制能够响应negotiate
消息,但若是经过respondsToSelector:
和isKindOfClass:
方法进行判断的话,依然是返回NO
的。若是想让这两个方法能够在判断negotiate
方法时返回YES
,须要重写这两个方法并在其中加入判断逻辑。
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
} else {
//
}
return NO;
}
复制代码
在执行forwardInvocation:
以前,须要经过methodSignatureForSelector:
方法返回方法签名,若是不实现则用默认的方法签名。
在方法签名的过程当中,能够将未实现的方法转发给其代理。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
复制代码
在项目中常常会出现由于调用未实现的方法,致使程序崩溃的状况。在学习消息转发后,就能够经过消息转发来解决这个问题。
全部的类的基类都是NSObject
类(NSProxy
除外),能够将NSObject
类的消息转发流程拦截,而后作一些统一的处理,这样就能够解决方法未实现致使的崩溃。根据Category
能够将原类方法“覆盖”的特色,能够在Category
中实现相应的拦截方法。
// 自定义类
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
- (void)testMethod;
@end
// 接收消息的IMP
void dynamicResolveMethod(id self, SEL _cmd) {
NSLog(@"method forward");
}
// 对NSObject建立的Category
@implementation NSObject (ExceptionForward)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
const char *types = sel_getName(sel);
class_addMethod([self class], sel, (IMP)dynamicResolveMethod, types);
return YES;
}
#pragma clang diagnostic pop
@end
复制代码
咱们的拦截方案是在resolveInstanceMethod
方法中,动态建立未实现的方法,并将IMP
统一设置为dynamicResolveMethod
函数进行处理。这样全部未实现的方法都会执行dynamicResolveMethod
函数,而不崩溃,在dynamicResolveMethod
函数中能够作崩溃统计等操做。
在OC中是不支持多继承的,可是能够经过消息转发模拟多继承。在子类中实例化多个父类,当消息发送过来的时候,在消息转发的方法中,将调用重定向到父类的实例对象中,以实现多继承的效果。
下面是多继承的例子,建立两个父类Cat
和Dog
,并将须要子类继承的方法都定义到Protocol
中,在Cat
和Dog
中实现Protocol
中的方法。
@protocol CatProtocol <NSObject>
- (void)eatFish;
@end
@interface Cat : NSObject <CatProtocol>
@end
@implementation Cat
- (void)eatFish {
NSLog(@"Cat Eat Fish");
}
@end
@protocol DogProtocol <NSObject>
- (void)eatBone;
@end
@interface Dog : NSObject
@end
@implementation Dog
- (void)eatBone {
NSLog(@"Dog Eat Bone");
}
@end
复制代码
子类直接经过遵照父类的协议,来表示本身“继承”哪些类,并在内部实例化对应的父类对象。在外界调用协议方法时,子类实际上是没有实现这些父类的方法的,因此经过转发方法将消息转发给响应的父类。
@interface TestObject : NSObject <CatProtocol, DogProtocol>
@end
@interface TestObject()
@property (nonatomic, strong) Cat *cat;
@property (nonatomic, strong) Dog *dog;
@end
@implementation TestObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.cat respondsToSelector:aSelector]) {
return self.cat;
} else if ([self.dog respondsToSelector:aSelector]) {
return self.dog;
} else {
return self;
}
}
// 忽略Cat和Dog的初始化过程
@end
复制代码
简书因为排版的问题,阅读体验并很差,布局、图片显示、代码等不少问题。因此建议到我Github
上,下载Runtime PDF
合集。把全部Runtime
文章总计九篇,都写在这个PDF
中,并且左侧有目录,方便阅读。
下载地址:Runtime PDF 麻烦各位大佬点个赞,谢谢!😁