深入研究Runtime(3)
LeoYfd
8年前
<p>本文介绍runtime的两个进阶用法:</p> <ol> <li>动态方法解析:如何动态地提供<code>方法的实现</code>?</li> <li>消息转发:发送消息时,如果消息接收者没有实现该方法,则运行时会报错。当消息接收者没有实现该方法时,应该如何实现将消息转发给<code>另一个</code>接收者?</li> </ol> <h2>动态方法解析</h2> <p>有一些情况,可能是需要动态提供方法的实现的,如下:</p> <ol> <li>编译器指令<code>@dynamic</code>:在 xcode4.5 之前,<code>@property</code> 只是声明了 setter/getter 方法,如果不使用<code>@synthesize</code>,则必须自己提供 setter/getter 或者 getter 方法。另外一个就是<code>@dynamic</code>,但是需动态添加 getter/setter 方法的实现。</li> </ol> <p>如何给指定方法动态添加实现?</p> <ul> <li> <p>在 <code>resolveInstanceMethod:</code> 和 <code>resolveClassMethod:</code> 中根据给定的方法名,使用<code>class_addMethod( )</code>把函数作为一个方法添加到类里。</p> <pre> <code class="language-objectivec">void dynamicMethodIMP(id self, SEL _cmd) { // 若方法有参数,则需在后面添加相应参数 // implementation ... 方法的实现 }</code></pre> </li> <li> <p>BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)</p> <ul> <li>cls:需要添加方法的类</li> <li>name:方法名</li> <li>imp:方法实现对应的函数,必须有 self 和 _cmd 两个参数</li> <li>types:C语言字符串,描述实现函数对应的参数类型( 包括返回值,第1个为描述返回值,因为2、3分别是id self,SEL _cmd,所以必须为"@:" )</li> </ul> </li> </ul> <pre> <code class="language-objectivec"> @implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { // 是否为resolveThisMethodDynamically方法 class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); // 动态添加方法 return YES; // 返回YES,则不会转发 } return [super resolveInstanceMethod:aSEL]; // 不是需要动态添加的方法,按照父类处理 } @end</code></pre> <blockquote> <p>动态添加方法实现与消息的转发是不可以同时存在的,动态添加优先于消息转发。如果实现了<code>resolveInstanceMethod:</code> 和 <code>resolveClassMethod:</code>,并且返回YES,则不会进行消息的转发。如果没有实现,则转发消息。</p> </blockquote> <p>补充:动态加载</p> <p>Objective-C的程序允许在运行时加载和链接新的类与分类,此时加载与开始时加载无异。<br> 动态加载类与分类一个比较重要的应用就是<code>加载动态库</code>。</p> <ul> <li>使用方法: <ol> <li>使用运行时函数<code>objc_loadModules</code>( 在<code>objc/objc-load.h</code>头文件中定义 ),动态加载 <code>Mach-O</code>文件里的Objective-C的功能模块(动态库)</li> <li>NSBundle类中提供了简便的接口进行动态加载 <code>Mach-O</code>文件</li> </ol> </li> </ul> <blockquote> <p><code>Mach-O</code>文件:是Mac和iOS系统的可执行二进制文件,类似于window中的.exe文件</p> </blockquote> <h2>消息转发</h2> <p>当给对象发送消息时,如果该对象没有实现相应的方法处理该消息,则会调用 <code>-forwardInvocation:</code> 方法。NSObject类中实现了该方法,默认调用 <code>-doesNotRecognizeSelector:</code>方法,所以如果继承于NSObject的类接收没有实现的消息,则会报<code>运行时错误</code>。<br> 所以,在<code>-forwardInvocation:</code> 方法中实现消息转发即可。</p> <ul> <li> <p>转发一条消息,在<code>-forwardInvocation:</code> 方法中需要做的事情:</p> <ul> <li>决定把消息转发给谁</li> <li> <p>传递原始的参数</p> <blockquote> <p>在<code>-forwardInvocation:</code> 方法中,会传递给该方法唯一一个参数,NSInvocation的对象( 包含了原始的消息和参数 )</p> </blockquote> <pre> <code class="language-objectivec">- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]]) // 如果指定对象实现了该方法 [anInvocation invokeWithTarget:someOtherObject]; // 给指定对象发送消息 else [super forwardInvocation:anInvocation]; // 按照父类处理 }</code></pre> </li> </ul> </li> </ul> <h2>转发与多重继承</h2> <p>如下图所示:</p> <p><img src="https://simg.open-open.com/show/d6f96b5a6563ede8a6afd5d7922aaae4.png"></p> <p>转发.png</p> <p>Warrior类没有实现<code>negotiate</code>方法。当接收<code>negotiate</code>消息时,会调用Diplomat的对象方法,并且会传递原始参数。所以,这个过程类似于继承调用方法的过程。而Warrior本来就有父类,所以这种现象相当于使用<code>转发机制</code><strong>“</strong> 实现了<strong>“</strong>多重继承。</p> <blockquote> <p>转发与继承的对比:<br> 继承是把所有父类的<strong>“</strong>能力<strong>”</strong>都集中到一个类中,而转发只是仅仅调用了一下方法,把问题分离出去。在一定程度上还是有很大区别的。</p> </blockquote> <h2>转发与继承</h2> <p>转发是模仿继承的。但是NSObject永远不会混淆这两个概念。就好像方法<code>-respondsToSelector:</code>和<code>-isKindOfClass:</code>只会在继承层次中有效,而在转发链中是无效的,永远只会返回<code>NO</code>。</p> <p>但是有些情况下,你也想转发链中返回的是<code>YES</code>。那样就必须重写<code>-respondsToSelector:</code>和<code>-isKindOfClass:</code>方法。</p> <p>如下重写<code>-respondsToSelector:</code>方法:</p> <pre> <code class="language-objectivec">- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) // 如果父类实现了,则返回YES return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. */ } return NO; }</code></pre> <p>当你如果想将转发完全模仿了继承,重写<code>-respondsToSelector:</code>和<code>-isKindOfClass:</code>方法是远远不够的。</p> <p>以下是大概上还需要重写的方法:</p> <ul> <li><code>instancesRespondToSelector:</code></li> <li>如果使用了协议,则需重写<code>conformsToProtocol:</code></li> <li>重写<code>methodSignatureForSelector:</code>方法,返回转发后的方法描述 <pre> <code class="language-objectivec">- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; // 如果父类实现了该方法,才会有方法的描述,返回父类的即可 if (!signature) { // 若没有,则返回转发对象的 signature = [surrogate methodSignatureForSelector:selector]; } return signature; }</code></pre> </li> </ul> <blockquote> <p>note:这是一个高级别的技术点,当真的没有其它解决方案时才会考虑使用转发机制。千万不要用来替代继承,先考虑使用继承。如果真的需要使用,要保证自己对<code>实行转发</code>和<code>接收转发</code>这两个类的所有行为有一个完全的理解。</p> </blockquote> <p>来自: <a href="/misc/goto?guid=4959671175425550890" rel="nofollow">http://www.jianshu.com/p/334e2605e4ee</a></p> <p> </p>