談Runtime機制和使用的整體化梳理

dahz6994 10年前發布 | 8K 次閱讀 Apple Swift開發

來自: http://www.jianshu.com/p/8916ad5662a2


相比“凌波微步”的swift,Object-C被譽為“如來神掌”。傳說Runtime就是支持這“如來神掌”說法的最好體現。聽起來總是這么的神秘高級,于是總能在各個論壇看到碎片資料,時間一長總記不住哪里是哪里,每次都要打開好幾個網頁。這種記不住象現顯然是知識體系還不完整重要體現。還是自己從Runtime的思想到動手代碼呈現上做出總結尚為上策。


學習流程圖
</div>

一.基本概念

  1. RunTime簡稱運行時,就是系統在運行的時候的一些機制,其中最主要的是消息機制。
  2. 對于C語言,函數的調用在編譯的時候會決定調用哪個函數( C語言的函數調用請看這里 )。編譯完成之后直接順序執行,無任何二義性。
  3. OC的函數調用成為消息發送。屬于動態調用過程。在編譯的時候并不能決定真正調用哪個函數(事實證明,在編 譯階段,OC可以調用任何函數,即使這個函數并未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
  4. 只有在真正運行的時候才會根據函數的名稱找 到對應的函數來調用。
  5. </ol> </blockquote>

    官網文檔還提供關于傳統和現代版本Runtime的說明

    1. In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.
    2. In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.
      In addition, the modern runtime supports instance variable synthesis for declared properties (see Declared Properties in The Objective-C Programming Language).
    3. </ol> </blockquote>

      二.知曉OC的方法調用在Runtime中具體的實現

      1.OC代碼調用一個方法

      [self.loginBt login];

      2.在編譯時RunTime會將上述代碼轉化成[發送消息]

      objc_msgSend(self.loginB,@selector(login));

      三.常見的作用

      既然是“如來神掌”,簡直可以無法無天啦,當街攔下一個人問道“這是馬還是鹿啊?”,那人看是Runtime大人懼怕道“Runtime大人,您說是馬就是馬,是鹿就是鹿~”。Runtime大快“wow哈哈哈~,見你乖巧,我也不為難于你。你缺頭驢是吧?,本大人現在造一頭送于你,遷回家便是!喔~哈哈哈”。

      呵呵,扯遠了,回到Runtime作用上。無所不能的事情就不一一介紹了,梳理下較為可能用的幾個地方:

      1. 動態的添加對象的成員變量和方法
      2. 動態交換兩個方法的實現
      3. 實現分類也可以添加屬性
      4. 實現NSCoding的自動歸檔和解檔
      5. 實現字典轉模型的自動轉換

      四.編寫代碼實現

      1. 動態變量控制

      1)Sense:
      Teacher: What's your name?  
      XiaoMing: My name is XiaoMing.  
      Teacher: Pardon?  
      XiaoMing: My name is __

      在程序當中,假設XiaoMing的name原來的值為XiaoMing,后來被Runtime偷換了一個名字叫Minggo。那么,Runtime是如何做到的呢?

      2)Step:

      ①動態獲取XiaoMing類中的所有屬性[當然包括私有]

      Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);

      ②遍歷屬性找到對應name字段

      const char *varName = ivar_getName(var);

      ③修改對應的字段值成Minggo

      object_setIvar(self.xiaoMing, var, @"Minggo");
      3)Show Code:
      -(void)answer{
          unsigned int count = 0;
          Ivar ivar = class_copyIvarList([self.xiaoMing class], &count);
          for (int i = 0; i<count; i++) {
              Ivar var = ivar[i];
              const char varName = ivar_getName(var);
              NSString *name = [NSString stringWithUTF8String:varName];

          if ([name isEqualToString:@"_englishName"]) {
              object_setIvar(self.xiaoMing, var, @"Minggo");
              break;
          }
      }
      NSLog(@"XiaoMing first answer is %@",self.xiaoMing.englishName);
      self.nameTf.text = self.xiaoMing.englishName;
      

      }</pre>

      2. 動態交換方法

      1)Sense:
      Teacher: What's your name?  
      XiaoMing: My name is XiaoMing.  
      Teacher: Pardon?  
      XiaoMing: My name is __

      在程序當中,假設XiaoMing的第一次回答為firstSay,后來被Runtime交換了一個名字叫secondSay的方法,最終再調用firstSay的時候,其實是調用了secondSay的實現。那么,Runtime是如何做到的呢?

      2)Step:

      ①動態找到firstSay和secondSay方法

      Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(firstSay));
      Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(secondSay));

      ②交換兩個方法

      method_exchangeImplementations(m1, m2);
      3)Show Code:
      -(void)answer{

      Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(firstSay));
      Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(secondSay));
      
      method_exchangeImplementations(m1, m2);
      NSString *secondName = [self.xiaoMing firstSay];
      
      self.nameTf.text = secondName;
      NSLog(@"XiaoMing:My name is %@",secondName);
      

      }</pre>

      3. 動態添加方法

      1)Sense:
      Teacher: Where is LiLei from?
      XiaoMing: I don't know.
      Teacher: Guess?.
      LiHua: He is from __

      在程序當中,假設XiaoMing的中沒有guess這個方法,后來被Runtime添加一個名字叫guess的方法,最終再調用guess方法做出相應。那么,Runtime是如何做到的呢?

      2)Step:

      ①動態給XiaoMing類中添加guess方法:

      class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");

      這里參數地方說明一下:
      (IMP)guessAnswer 意思是guessAnswer的地址指針;
      "v@:" 意思是,v代表無返回值void,如果是i則代表int;@代表 id sel; : 代表 SEL _cmd;
      “v@:@@” 意思是,兩個參數的沒有返回值。

      ②調用guess方法響應事件:

      [self.xiaoMing performSelector:@selector(guess)];

      ③編寫guessAnswer的實現:

      void guessAnswer(id self,SEL _cmd){
          NSLog(@"He is from GuangTong");   
      }

      這個有兩個地方留意一下:
      1.void的前面沒有+、-號,因為只是C的代碼。
      2.必須有兩個指定參數(id self,SEL _cmd)

      3)Show Code:
      -(void)answer{
          class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
          if ([self.xiaoMing respondsToSelector:@selector(guess)]) {

          [self.xiaoMing performSelector:@selector(guess)];
      
      } else{
          NSLog(@"Sorry,I don't know");
      }
      self.cityTf.text = @"GuangTong";
      

      }

      void guessAnswer(id self,SEL _cmd){

      NSLog(@"He is from GuangTong");
      
      

      }</pre>

      4. 動態為Category擴展加屬性

      這一點上有兩點要表達一下:第一,XCode運行你在Category的.h文件申明@Property,編譯通過,但運行時如果沒有Runtime處理,進行賦值取值,就馬上報錯。第二,這一點是iOS面試當中經常面到的問題:如何給擴展添加屬性?。

      1)Sense:
      Teacher: What's your Chinese name?
      XiaoMing: I have no one.
      LiHua: You should have one.
      LiHua: Your Chinese name is __

      在程序當中,假設XiaoMing的中沒有chineseName這個屬性,后來被Runtime添加一個名字叫chineseName的屬性。那么,Runtime是如何做到的呢?

      2)Step:

      ①申明chineseName屬性

      #import "XiaoMing.h"

      @interface XiaoMing (MutipleName)

      @property(nonatomic,copy) NSString *chineseName;

      @end</pre>

      ②動態添加屬性和實現方法

      #import "XiaoMing+MutipleName.h"

      import <objc/runtime.h>

      @implementation XiaoMing (MutipleName)

      char cName;

      -(void)setChineseName:(NSString *) chineseName{ objc_setAssociatedObject(self, &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC); }

      -(NSString *)chineseName{ return objc_getAssociatedObject(self, &cName); }

      @end</pre>

      ③使用chineseName屬性

      -(void)answer{
          NSLog(@"My Chinese name is %@",self.xiaoMing.chineseName);
          self.chineseNameTf.text = self.xiaoMing.chineseName;
      }
      3)Show Code:

      上邊就是最要的Code了。以下更精彩。

      五.效果圖更直觀


      </div>

      六.源碼下載地址更詳細

      https://github.com/minggo620/iOSRuntimeLearn.git

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