深入研究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>