深入研究Runtime(3)

LeoYfd 8年前發布 | 7K 次閱讀 iOS開發 移動開發 IOS

本文介紹runtime的兩個進階用法:

  1. 動態方法解析:如何動態地提供方法的實現
  2. 消息轉發:發送消息時,如果消息接收者沒有實現該方法,則運行時會報錯。當消息接收者沒有實現該方法時,應該如何實現將消息轉發給另一個接收者?

動態方法解析

有一些情況,可能是需要動態提供方法的實現的,如下:

  1. 編譯器指令@dynamic:在 xcode4.5 之前,@property 只是聲明了 setter/getter 方法,如果不使用@synthesize,則必須自己提供 setter/getter 或者 getter 方法。另外一個就是@dynamic,但是需動態添加 getter/setter 方法的實現。

如何給指定方法動態添加實現?

  • resolveInstanceMethod:resolveClassMethod: 中根據給定的方法名,使用class_addMethod( )把函數作為一個方法添加到類里。

    void dynamicMethodIMP(id self, SEL _cmd) {  // 若方法有參數,則需在后面添加相應參數
      // implementation ... 方法的實現
    }
  • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

    • cls:需要添加方法的類
    • name:方法名
    • imp:方法實現對應的函數,必須有 self 和 _cmd 兩個參數
    • types:C語言字符串,描述實現函數對應的參數類型( 包括返回值,第1個為描述返回值,因為2、3分別是id self,SEL _cmd,所以必須為"@:" )
  @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

動態添加方法實現與消息的轉發是不可以同時存在的,動態添加優先于消息轉發。如果實現了resolveInstanceMethod:resolveClassMethod:,并且返回YES,則不會進行消息的轉發。如果沒有實現,則轉發消息。

補充:動態加載

Objective-C的程序允許在運行時加載和鏈接新的類與分類,此時加載與開始時加載無異。
動態加載類與分類一個比較重要的應用就是加載動態庫

  • 使用方法:
    1. 使用運行時函數objc_loadModules( 在objc/objc-load.h頭文件中定義 ),動態加載 Mach-O文件里的Objective-C的功能模塊(動態庫)
    2. NSBundle類中提供了簡便的接口進行動態加載 Mach-O文件

Mach-O文件:是Mac和iOS系統的可執行二進制文件,類似于window中的.exe文件

消息轉發

當給對象發送消息時,如果該對象沒有實現相應的方法處理該消息,則會調用 -forwardInvocation: 方法。NSObject類中實現了該方法,默認調用 -doesNotRecognizeSelector:方法,所以如果繼承于NSObject的類接收沒有實現的消息,則會報運行時錯誤
所以,在-forwardInvocation: 方法中實現消息轉發即可。

  • 轉發一條消息,在-forwardInvocation: 方法中需要做的事情:

    • 決定把消息轉發給誰
    • 傳遞原始的參數

      -forwardInvocation: 方法中,會傳遞給該方法唯一一個參數,NSInvocation的對象( 包含了原始的消息和參數 )

      - (void)forwardInvocation:(NSInvocation *)anInvocation
      {
          if ([someOtherObject respondsToSelector:[anInvocation selector]])   // 如果指定對象實現了該方法
          [anInvocation invokeWithTarget:someOtherObject];  // 給指定對象發送消息
          else
          [super forwardInvocation:anInvocation];  // 按照父類處理
      }

轉發與多重繼承

如下圖所示:

轉發.png

Warrior類沒有實現negotiate方法。當接收negotiate消息時,會調用Diplomat的對象方法,并且會傳遞原始參數。所以,這個過程類似于繼承調用方法的過程。而Warrior本來就有父類,所以這種現象相當于使用轉發機制 實現了多重繼承。

轉發與繼承的對比:
繼承是把所有父類的能力都集中到一個類中,而轉發只是僅僅調用了一下方法,把問題分離出去。在一定程度上還是有很大區別的。

轉發與繼承

轉發是模仿繼承的。但是NSObject永遠不會混淆這兩個概念。就好像方法-respondsToSelector:-isKindOfClass:只會在繼承層次中有效,而在轉發鏈中是無效的,永遠只會返回NO

但是有些情況下,你也想轉發鏈中返回的是YES。那樣就必須重寫-respondsToSelector:-isKindOfClass:方法。

如下重寫-respondsToSelector:方法:

- (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;
}

當你如果想將轉發完全模仿了繼承,重寫-respondsToSelector:-isKindOfClass:方法是遠遠不夠的。

以下是大概上還需要重寫的方法:

  • instancesRespondToSelector:
  • 如果使用了協議,則需重寫conformsToProtocol:
  • 重寫methodSignatureForSelector:方法,返回轉發后的方法描述
    - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
    {
        NSMethodSignature* signature = [super methodSignatureForSelector:selector];  // 如果父類實現了該方法,才會有方法的描述,返回父類的即可
        if (!signature) {  // 若沒有,則返回轉發對象的
             signature = [surrogate methodSignatureForSelector:selector];
        }
        return signature;
    }

note:這是一個高級別的技術點,當真的沒有其它解決方案時才會考慮使用轉發機制。千萬不要用來替代繼承,先考慮使用繼承。如果真的需要使用,要保證自己對實行轉發接收轉發這兩個類的所有行為有一個完全的理解。

來自: http://www.jianshu.com/p/334e2605e4ee

 

 本文由用戶 LeoYfd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!