内容简介:当一个对象的方法被调用时,首先在对象所属的类中查找方法列表,如果当前类中没有则向父类查找,一直找到根类当一个方法没有实现时,也就是在
<简书 — 刘小壮> https://www.jianshu.com/p/f313e8e32946
当一个对象的方法被调用时,首先在对象所属的类中查找方法列表,如果当前类中没有则向父类查找,一直找到根类 NSObject
。如果始终没有找到方法实现,则进入消息转发步骤中。
动态消息解析
当一个方法没有实现时,也就是在 cache lsit
和其继承关系的 method list
中,没有找到对应的方法。这时会进入消息转发阶段,但是在进入消息转发阶段前, Runtime
会给一次机会动态添加方法实现。
可以通过重写 resolveInstanceMethod:
和 resolveClassMethod:
方法,动态添加未实现的方法。其中第一个是添加实例方法,第二个是添加类方法。这两个方法都有一个 BOOL
返回值,返回 NO
则进入消息转发机制。
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
对应的函数实现,具体的描述可以参考官方文档。
Message Forwarding
在进行消息转发之前,还可以在 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:
即可。
- (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 麻烦各位大佬点个赞,谢谢!:grin:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Numerical Linear Algebra
Lloyd N. Trefethen、David Bau III / SIAM: Society for Industrial and Applied Mathematics / 1997-06-01 / USD 61.00
Numerical Linear Algebra is a concise, insightful, and elegant introduction to the field of numerical linear algebra.一起来看看 《Numerical Linear Algebra》 这本书的介绍吧!