RunTime和RunLoop

RunLoop

  • runloop是事件的接受的分發機制的實現
  • runloop提供一種異步執行代碼的機制,不能並行執行任務
  • 在主隊列中,main Runloop直接配合任務的執行,負責處理UI事件, 定時器以及其餘內核相關的事件

Runloop 的主要目的

  • 保證程序執行時不會被系統終止
  • 何時使用Runloop
  • 當須要和該線程進行交互的時候纔會使用Runloop
  • 滅一個線程都有對應的的Runloop,可是默認的非主線程的Runloop是不執行,須要爲Runloop添加至少一個事件源,而後去run。
  • 通常狀況下咱們不去作這些,除非你要長久去檢測單獨線程中的某個事件
  • Runloop,是線程進入好被線程用來響應事件以及調用事件處理函數的地方,須要在代碼中使用控制語句實現Runloop的循環,須要代碼提供while或者for來驅動Runloop在這個循環中,使用一個Runloop對象[NSRunLoop CurrentRunLoop]執行接收消息,調用對應的處理函數

Runloop接受兩種數據源事件:Input 和timer Source

Input Source傳遞事件,一般是來自其餘線程和不一樣線程和不一樣程序中的消息數組

Timer Source (定時器)傳遞同步事件(重複執行或者在特定時間上觸發)緩存

處理 input Source Runloop 也會產生一些關於自己的notification,註冊Runloop的observe,能夠接收這些notification,作一些額外的處理。(使用CoreFundation來成爲Runloop的observe)bash

Runloop工做特色

當有時間發生時,Runloop會根據具體的事件類型通知相應程序做出相應網絡

當沒有事件發生時,Runloop會進入休眠狀態,從而達到省電的目的數據結構

當事件再次發生時,Runloop會被喚醒,處理事件app

Runloop組成

  1. 與線程和自動釋放遲有關異步

  2. CFRunLoopRef構造:數據結構;建立與退出;mode切換和item依賴;Runloop啓動async

    • CFRunLoopModeRef:數據結構(與CFRunLoopRef放一塊兒了);建立;類型;
    • modeItems:
      • CFRunLoopSourceRef:數據結構(source0/source1);
        • source0 :
        • source1 :
    • CFRunLoopTimerRef:數據結構;建立與生效;相關類型(GCD的timer與CADisplayLink)
    • CFRunLoopObserverRef:數據結構;建立與添加;監聽的狀態;
  3. Runloop內部邏輯:關鍵在兩個判斷點(是否睡覺,是否退出)函數

    • 代碼實現:
    • 函數做用棧顯示:
  4. Runloop本質:mach port和mach_msg()。oop

  5. 如何處理事件:

    • 界面刷新:
    • 手勢識別:
    • GCD任務:
    • timer:(與CADisplayLink)
    • 網絡請求:
  6. 應用:

    • 滑動與圖片刷新;
    • 常駐子線程,保持子線程一直處理事件

線程(建立)-->runloop將進入-->最高優先級OB建立釋放池-->runloop將睡-->最低優先級OB銷燬舊池建立新池-->runloop將退出-->最低優先級OB銷燬新池-->線程(銷燬)

RunLoop 的構造

CFRunLoopRef構造:
// runloop數據結構
struct __CFRunLoopMode {
    CFStringRef _name;            // Mode名字, 
    CFMutableSetRef _sources0;    // Set<CFRunLoopSourceRef>
    CFMutableSetRef _sources1;    // Set<CFRunLoopSourceRef>
    CFMutableArrayRef _observers; // Array<CFRunLoopObserverRef>
    CFMutableArrayRef _timers;    // Array<CFRunLoopTimerRef>
...
};
// mode數據結構
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set<CFStringRef>
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set<CFRunLoopModeRef>
...
};
複製代碼

建立與退出: model切換和item的依賴

  1. 主線程的runloop自動建立,子線程的runloop默認不建立(在子線程中調用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 獲取RunLoop對象的時候,就會建立RunLoop);

  2. runloop退出的條件:app退出;線程關閉;設置最大時間到期;modeItem爲空;

  3. 同一時間一個runloop只能在一個mode,切換mode只能退出runloop,再重進指定mode(隔離modeItems使之互不干擾);

  4. 一個item能夠加到不一樣mode;一個mode被標記到commonModes裏(這樣runloop不用切換mode)。

RunLoop 啓動

  • 用DefaultMode啓動

    void CFRunLoopRun(void) {
         CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
     }
    複製代碼
  • 用指定的Mode啓動,容許設置RunLoop最大時間(假無限循環),執行完畢是否退出

    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
    複製代碼
  • RunLoop 自動建立對應的model,model 只能添加不能刪除

    添加model CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    複製代碼

    類型

    1. kCFRunLoopDefaultMode: 默認 mode,一般主線程在這個 Mode 下運行。
    2. UITrackingRunLoopMode: 追蹤mode,保證Scrollview滑動順暢不受其餘 mode 影響。
    3. UIInitializationRunLoopMode: 啓動程序後的過渡mode,啓動完成後就再也不使用。
    4. GSEventReceiveRunLoopMode: Graphic相關事件的mode,一般用不到。
    5. kCFRunLoopCommonModes: 佔位mode,做爲標記DefaultMode和CommonMode用。

    modelItems

    // 添加移除item的函數(參數:添加/移除哪一個item到哪一個runloop的哪一個mode下)
     CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
     
     CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    
     CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    
     CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    複製代碼

    CFRunLoopSourceRef:事件來源

    按照官方文檔CFRunLoopSourceRef爲3類,但數據結構只有兩類(???)
    Port-Based Sources:與內核端口相關
    Custom Input Sources:與自定義source相關
    Cocoa Perform Selector Sources:與PerformSEL方法相關)
    複製代碼

CFRunLoopObserverRef:監聽runloop狀態,接收回調信息(常見於自動釋放池建立銷燬)

// 第一個參數用於分配該observer對象的內存空間
  // 第二個參數用以設置該observer監聽什麼狀態
  // 第三個參數用於標識該observer是在第一次進入run loop時執行仍是每次進入run loop處理時均執行
  // 第四個參數用於設置該observer的優先級,通常爲0
  // 第五個參數用於設置該observer的回調函數
  // 第六個參數observer的運行狀態   
  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
     // 執行代碼
  }

  typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
      kCFRunLoopEntry         = (1UL << 0), // 即將進入Loop
      kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
      kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
      kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
      kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
      kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
  };
複製代碼

Runloop內部邏輯:關鍵在兩個判斷點(是否睡覺,是否退出)

int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

  // 0.1 根據modeName找到對應mode
  CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
  // 0.2 若是mode裏沒有source/timer/observer, 直接返回。
  if (__CFRunLoopModeIsEmpty(currentMode)) return;

  // 1.1 通知 Observers: RunLoop 即將進入 loop。---(OB會建立釋放池)
  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

  // 1.2 內部函數,進入loop
  __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

      Boolean sourceHandledThisLoop = NO;
      int retVal = 0;
      do {

          // 2.1 通知 Observers: RunLoop 即將觸發 Timer 回調。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
          // 2.2 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
          // 執行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.3 RunLoop 觸發 Source0 (非port) 回調。
          sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
          // 執行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.4 若是有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 而後跳轉去處理消息。
          if (__Source0DidDispatchPortLastTime) {
              Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
              if (hasMsg) goto handle_msg;
          }

          // 3.1 若是沒有待處理消息,通知 Observers: RunLoop 的線程即將進入休眠(sleep)。--- (OB會銷燬釋放池並創建新釋放池)
          if (!sourceHandledThisLoop) {
              __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
          }

          // 3.2. 調用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
          // -  一個基於 port 的Source1 的事件。
          // -  一個 Timer 到時間了
          // -  RunLoop 啓動時設置的最大超時時間到了
          // -  被手動喚醒
          __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
              mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
          }

          // 3.3. 被喚醒,通知 Observers: RunLoop 的線程剛剛被喚醒了。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

          // 4.0 處理消息。
          handle_msg:

          // 4.1 若是消息是Timer類型,觸發這個Timer的回調。
          if (msg_is_timer) {
              __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
          } 

          // 4.2 若是消息是dispatch到main_queue的block,執行block。
          else if (msg_is_dispatch) {
              __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
          } 

          // 4.3 若是消息是Source1類型,處理這個事件
          else {
              CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
              sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
              if (sourceHandledThisLoop) {
                  mach_msg(reply, MACH_SEND_MSG, reply);
              }
          }

          // 執行加入到Loop的block
          __CFRunLoopDoBlocks(runloop, currentMode);


          // 5.1 若是處理事件完畢,啓動Runloop時設置參數爲一次性執行,設置while參數退出Runloop
          if (sourceHandledThisLoop && stopAfterHandle) {
              retVal = kCFRunLoopRunHandledSource;
          // 5.2 若是啓動Runloop時設置的最大運轉時間到期,設置while參數退出Runloop
          } else if (timeout) {
              retVal = kCFRunLoopRunTimedOut;
          // 5.3 若是啓動Runloop被外部調用強制中止,設置while參數退出Runloop
          } else if (__CFRunLoopIsStopped(runloop)) {
              retVal = kCFRunLoopRunStopped;
          // 5.4 若是啓動Runloop的modeItems爲空,設置while參數退出Runloop
          } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
              retVal = kCFRunLoopRunFinished;
          }

          // 5.5 若是沒超時,mode裏沒空,loop也沒被中止,那繼續loop,回到第2步循環。
      } while (retVal == 0);
  }

  // 6. 若是第6步判斷後loop退出,通知 Observers: RunLoop 退出。--- (OB會銷燬新釋放池)
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

複製代碼

一步一步寫具體的實現邏輯過於繁瑣不便理解,按Runloop狀態大體分爲:

  1. Entry:通知OB(建立pool);
  2. 執行階段:按順序通知OB並執行timer,source0;如有source1執行source1;
  3. 休眠階段:利用mach_msg判斷進入休眠,通知OB(pool的銷燬重建);被消息喚醒通知OB;
  4. 執行階段:按消息類型處理事件;
  5. 判斷退出條件:若是符合退出條件(一次性執行,超時,強制中止,modeItem爲空)則退出,不然回到第2階段;
  6. Exit:通知OB(銷燬pool)

Runloop本質:mach port和mach_msg()。

Mach是XNU的內核,進程、線程和虛擬內存等對象經過端口發消息進行通訊,Runloop經過mach_msg()函數發送消息,若是沒有port 消息,內核會將線程置於等待狀態 mach_msg_trap() 。若是有消息,判斷消息類型處理事件,並經過modeItem的callback回調。
Runloop有兩個關鍵判斷點,一個是經過msg決定Runloop是否等待,一個是經過判斷退出條件來決定Runloop是否循環。
複製代碼

如何處理事件

1.界面處理

  • 當UI改變( Frame變化、 UIView/CALayer 的繼承結構變化等)時,或手動調用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法後,這個 UIView/CALayer 就被標記爲待處理。
  • 蘋果註冊了一個用來監聽BeforeWaiting和Exit的Observer,在它的回調函數裏會遍歷全部待處理的 UIView/CAlayer 以執行實際的繪製和調整,並更新 UI 界面。
  1. 事件響應
  • 當一個硬件事件(觸摸/鎖屏/搖晃/加速等)發生後,首先由 IOKit.framework 生成一個 IOHIDEvent 事件並由 SpringBoard 接收, 隨後由mach port 轉發給須要的App進程。
  • 蘋果註冊了一個 Source1 (基於 mach port 的) 來接收系統事件,經過回調函數觸發Sourece0(因此UIEvent其實是基於Source0的),調用 _UIApplicationHandleEventQueue() 進行應用內部的分發。
  • _UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理幷包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。
  1. 手勢識別
  • 若是上一步的 _UIApplicationHandleEventQueue() 識別到是一個guesture手勢,會調用Cancel方法將當前的touchesBegin/Move/End 系列回調打斷。隨後系統將對應的 UIGestureRecognizer 標記爲待處理。
  • 蘋果註冊了一個 Observer 監測 BeforeWaiting (Loop即將進入休眠) 事件,其回調函數爲_UIGestureRecognizerUpdateObserver(),其內部會獲取全部剛被標記爲待處理的 GestureRecognizer,並執行GestureRecognizer的回調。
  • 當有 UIGestureRecognizer 的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。

4.GCD任務

  • 當調用 dispatch_async(dispatch_get_main_queue(), block) 時,libDispatch 會向主線程的 RunLoop 發送消息,RunLoop會被喚醒,並從消息中取得這個 block,並在回調裏執行這個 block。Runloop只處理主線程的block,dispatch 到其餘線程仍然是由 libDispatch 處理的。

RunTime

Runtime就是系統在運行的時候的一種機制,其中最重要的就是消息機制,對於C語言,函數的調用在編譯的時候就會決定調用那個函數C語言的函數調用請看這裏 
)。編譯完成以後直接順序執行,無任何二義性。OC的函數調用成爲消息發送。屬於動態調用過程。在編譯的時候並不能決定真正調用哪一個函數(事實證實,在編
譯階段,OC能夠調用任何函數,即便這個函數並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。只有在真正運行的時候纔會根據函數的名稱找
到對應的函數來調用。
複製代碼
  1. 封裝:在這個庫中,對象能夠用C語言中的結構體表示,而方法能夠用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,咱們就能夠在程序運行時建立,檢查,修改類、對象和它們的方法了。

Runtime主要實現思路

實例對象instance->類class->方法method(->SEL->IMP)->實現函數
實例對象只存放ISA指針和實例變量,由ISA指針找到所屬類,類維護一個運行時可接收的方法列表;方法列表中的每一個入口是一個方法(Method),其中key是一個特定名稱,即選擇器(SEL),其對應一個指向底層C實現函數的指針,即實現(IMP),。運行時機制最關鍵核心是objc_msgSend函數,經過給target(類)發送selecter(SEL)來傳遞消息,找到匹配的IMP,指向實現的C函數。
因爲OC的運行時動態特性,在編譯以後能夠在運行時經過C操做函數,動態地建立修改類信息,動態綁定方法和重寫實現,靈活地實現一些自定義功能。
在oc中消息機制如何調用
複製代碼

RunTime 介紹

  1. 類的本質
- 類相關:
    + 數據類型:class,object;
                 - isa 元類
                 - superClass 根類
    + 操做函數:
       - class_:
           + get: 類名,父類; 實例變量,成員變量;屬性;實例方法,類方法,方法實現;
           + copy: 成員變量列表;屬性列表;方法列表;協議列表;
           + add: 成員變量;屬性;方法;協議;
           + replace:屬性;方法;
           + respond:響應方法判斷(內省)
           + isMetaclass:元類判斷(內省)
           + conform:遵循協議判斷(內省)
       - objc_:
           + get: 實例變量;成員變量;類名;類;元類;關聯對象;
           + copy: 對象;類;類列表;協議列表;
           + set: 實例變量;成員變量;類;類列表;協議;關聯對象;
           + dispose: 對象;
           - 動態建立/銷燬類、對象
- 成員變量、屬性相關:
+ 數據類型:Ivar;objc_property_t;objc_property_attribute_t;
          + 操做函數:
           - ivar_:
           - property_:
- 方法消息相關:
   + 數據類型:SEL;IMP; Method;方法緩存
   + 操做函數: 
        - method_:
            + invoke: 方法實現的返回值;
            + get: 方法名;方法實現;參數與返回值相關;
            + set:方法實現;
            + exchange:交換方法實現
    + 方法調用:msgSend函數(找到方法實現)
    + 消息轉發:
            - Method Resolution
- Fast Forwarding
- Normal Forwarding
- 協議相關:
    + 數據類型:Protocol;
    + 操做函數:
        - protocol_:
             + get: 協議;屬性;
             + copy:協議列表;屬性列表;
             + add:屬性;方法;協議;
             + isEqual:判斷兩協議等同;
             + comform:判斷是否遵循協議;
 - 其餘:類名;版本號;類信息;(忽略)
複製代碼

動態實現方法交換

Method Swizzling
   具體方法:方法交還,也可作方法攔截
//靜態就交換靜態,實例方法就交換實例方法
void Swizzle(Class c, SEL origSEL, SEL newSEL) {
   Method origMethod = class_getInstanceMethod(c, origSEL);
   Method newMethod = nil;
   if (!origMethod) {
   	origMethod = class_getClassMethod(c, origSEL);
       if (!origMethod) {
           return;
       }
       newMethod = class_getClassMethod(c, newSEL);
       if (!newMethod) {
           return;
       }
   } else {
       newMethod = class_getInstanceMethod(c, newSEL);
       if (!newMethod) {
           return;
       }
   }
   
   //自身已經有了就添加不成功,直接交換便可
   if(class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
       class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
   } else {
       method_exchangeImplementations(origMethod, newMethod);
   }
}
複製代碼

runtime對類分析

@interface NSObject<nsobject>{

Class isa OBJC_ISA_AVAILABILITY;
}</nsobject>

在NSObject中存在一個Class 指針, 而後咱們看Class

typedef struct objc_Class *Class;

struct objc_class{
Class isa;//指向metaclass

Class super_class;//指向父類
Const char *name;// 類名

Long version;//版本信息,初始化默認是0,能夠根據runtime函數class_setVersion和class_getVersion進行修改、讀取
Long info一些標識信息,如CLS_CLASS (0x1L)
表示該類爲普通 class ,其中包含對象方法和成員變量;CLS_META (0x2L)
表示該類爲 metaclass,其中包含類方法; 
  long instance_size ; // 該類的實例變量大小(包括從父類繼承下來的實例變量);  
struct objc_ivar_list *ivars; // 用於存儲每一個成員變量的地址  
struct objc_method_list **methodLists ; // 與 info 的一些標誌位有關,如CLS_CLASS (0x1L),則存儲對象方法,如CLS_META (0x2L),則存儲類方法;  
 struct objc_cache *cache; // 指向最近使用的方法的指針,用於提高效率;
 struct objc_protocol_list *protocols; // 存儲該類遵照的協議  
}


Class 
isa:指向metaclass,也就是靜態的Class。通常一個Obj對象中的isa會指向普通的Class,這個Class中存儲普通成員變量和對
象方法(「-」開頭的方法),普通Class中的isa指針指向靜態Class,靜態Class中存儲static類型成員變量和類方法(「+」開頭的方
法)。

複製代碼
  1. 數據類型:
|isa和super_class :不一樣的類中能夠有相同的方法(同一個類的方法不能同名,哪怕參數類型不一樣,後面解釋...),因此要先肯定是那個類。isa和super_class是找到實現函數的關鍵映射,決定找到存放在哪一個類的方法實現。(isa用於自省肯定所屬類,super_class肯定繼承關係)。
實例對象的isa指針指向類,類的isa指針指向其元類(metaClass)。對象就是一個含isa指針的結構體。類存儲實例對象的方法列表,元類存儲類的方法列表,元類也是類對象。
複製代碼
  1. 操做函數:類對象以class_爲前綴,實例對象以object_爲前綴
  • class
get: 類名,父類,元類;實例變量,成員變量;屬性;實例方法,類方法,方法實現;
// 獲取類的類名
const char * class_getName ( Class cls );
// 獲取類的父類
Class class_getSuperclass ( Class cls );

// 獲取實例大小
size_t class_getInstanceSize ( Class cls );
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

複製代碼
  • copy 成員變量列表;屬性列表;方法列表;協議列表;
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 獲取全部方法的列表
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 獲取類實現的協議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount )

複製代碼
  • add: 成員變量;屬性;方法;協議;(添加成員變量只能在運行時建立的類,且不能爲元類)
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 添加協議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
複製代碼
  • replace: 屬性和方法
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
複製代碼
  • reponse:響應方法判斷
// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
複製代碼
  • isMetaCLass: 元類判斷
// 判斷給定的Class是不是一個元類
BOOL class_isMetaClass ( Class cls );
複製代碼
  • conform: 遵循協議判斷
// 返回類是否實現指定的協議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
複製代碼
  • objc_ //對象
get: 實例變量;成員變量;類名;類;元類;關聯對象
// 獲取對象實例變量
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 獲取對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 獲取對象的類名
const char * object_getClassName ( id obj );
// 獲取對象的類
Class object_getClass ( id obj );
Class objc_getClass ( const char *name );
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );
//獲取關聯對象
id objc_getAssociatedObject(self, &myKey);
複製代碼
  • copy : 對象、類、類列表、協議列表
// 獲取指定對象的一份拷貝
id object_copy ( id obj, size_t size );
// 建立並返回一個指向全部已註冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );

複製代碼
  • set: 實例變量、類、類列表、協議、關聯對象
// 設置類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 設置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
//設置關聯對象.其中myKey本身在用的時候須要在設置一個,在運行時的時候它是經過這個未標記的,裏面有一套完善的機制,就是你在實現kvo的時候也是能夠的
void objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
複製代碼
  • dispose: 對象
// 釋放指定對象佔用的內存
id object_dispose ( id obj );
複製代碼

動態建立/銷燬類、對象

  • 動態建立/銷燬類:
// 建立一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );

// 銷燬一個類及其相關聯的類
void objc_disposeClassPair ( Class cls );

// 在應用中註冊由objc_allocateClassPair建立的類
void objc_registerClassPair ( Class cls );
複製代碼
  • 動態建立/銷燬對象:
// 建立類實例
id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置建立類實例
id objc_constructInstance ( Class cls, void *bytes );

// 銷燬類實例
void * objc_destructInstance ( id obj );
Class super_class:指向父類,若是這個類是根類,則爲NULL。
下面一張圖片很好的描述了類和對象的繼承關係:
複製代碼

注意:全部metaclass中isa指針都指向跟metaclass。而跟metaclass則指向自身。
Root metaclass是經過繼承Root class產生的。與root class結構體成員一致,也就是前面提到的結構。不一樣的是Root 
metaclass的isa指針指向自身。

上圖的解釋

一、isa:實例對象->類->元類->(不通過父元類)直接到根元類(NSObject的元類),根元類的isa指向本身;

二、 superclass:類->父類->...->根類NSObject,元類->父元類->...->根元類->根類,NSObject的superclass指向nil。
@selector (makeText):

這是一個SEL方法選擇器。SEL其主要做用是快速的經過方法名字(makeText)查找到對應方法的函數指針,而後調用其函數。SEL其自己是一個
Int類型的一個地址,地址中存放着方法的名字。對於一個類中。每個方法對應着一個SEL。因此iOS類中不能存在2個名稱相同的方法,即便參數類型不
同,由於SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。
這就是爲何  oc中不支持方法重載,由於函數指針只是根據函數名來生成的,在調用的時候,在去經過映射去找,
objc_msgSend(self, @selector(makeText));
void makeText (id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 第一個參數:給哪一個類添加方法
        // 第二個參數:添加方法的方法編號
        // 第三個參數:添加方法的函數實現(函數地址)
        // 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
下面咱們就來看看具體消息發送以後是怎麼來動態查找對應的方法的。

        首先,編譯器將代碼[obj makeText];轉化爲objc_msgSend(obj, @selector 
(makeText));,在objc_msgSend函數中。首先經過obj的isa指針找到obj對應的class。在Class中先去cache中
經過SEL查找對應函數method(猜想cache中method列表是以SEL爲key經過hash表來存儲的,這樣能提升函數查找速度),若
cache中未找到。再去methodList中查找,若methodlist中未找到,則取superClass中查找。若能找到,則將method加
入到cache中,以方便下次查找,並經過method中的函數指針跳轉到對應的函數中去執行。
複製代碼

實例變量、屬性相關

  1. 實例變量和屬性也是類對象的關鍵配置。
  • 數據類型:
Ivar;
typedef struct objc_ivar *Ivar;

struct objc_ivar {
  char *ivar_name                 OBJC2_UNAVAILABLE;  // 變量名
  char *ivar_type                 OBJC2_UNAVAILABLE;  // 變量類型
  int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字節
#ifdef __LP64__
  int space                       OBJC2_UNAVAILABLE;
#endif
}
objc_property_attribute_t(屬性的特性有:返回值、是否爲atomic、getter/setter名字、是否爲dynamic、背後使用的ivar名字、是否爲弱引用等)
typedef struct {
  const char *name;           // 特性名
  const char *value;          // 特性值
} objc_property_attribute_t;
複製代碼
  • 操做函數
ivar_:
get:
// 獲取成員變量名
const char * ivar_getName ( Ivar v );

// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );

// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );

property
// 獲取屬性名
const char * property_getName ( objc_property_t property );

// 獲取屬性特性描述字符串
const char * property_getAttributes ( objc_property_t property );

// 獲取屬性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );

// 獲取屬性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
複製代碼

方法消息機制相關

  • SEL

    SEL又叫選擇器,是表示一個方法的selector的指針,映射方法的名字。Objective-C在編譯時,會依據每個方法的名字、參數序列,生成一個惟一的整型標識(Int類型的地址),這個標識就是SEL。 SEL的做用是做爲IMP的KEY,存儲在NSSet中,便於hash快速查詢方法。SEL不能相同,對應方法能夠不一樣。因此在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,就算參數類型不一樣。多個方法能夠有同一個SEL。 不一樣的類能夠有相同的方法名。不一樣類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找本身對應的IMP 相關概念:類型編碼(Type Encoding) 編譯器將每一個方法的返回值和參數類型編碼爲一個字符串,並將其與方法的selector關聯在一塊兒。可使用@encode編譯器指令來獲取它。

    typedef struct objc_selector *SEL; <objc/runtime.h>中沒有公開具體的objc_selector結構體成員。

    但經過log可知SEL本質是一個字符串。

  • IMP

    IMP是指向實現函數的指針,經過SEL取得IMP後,咱們就得到了最終要找的實現函數的入口

    typedefine id (*IMP)(id, SEL, ...)

  • Method

    這個結構體至關於在SEL和IMP之間做了一個綁定。這樣有了SEL,咱們即可以找到對應的IMP,從而調用方法的實現代碼。(在運行時纔將SEL和IMP綁定, 動態配置方法)
    複製代碼

typedef struct objc_method *Method;

struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; // 參數類型 IMP method_imp OBJC2_UNAVAILABLE; // 方法實現 } objc_method_list 就是用來存儲當前類的方法鏈表,objc_method存儲了類的某個方法的信息。 struct objc_method_list { struct objc_method_list obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef LP64 int space OBJC2_UNAVAILABLE; #endif / variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; }

```
複製代碼
  • 方法緩存

    方法調用最早是在方法緩存裏找的,方法調用是懶調用,第一次調用時加載後加到緩存池裏。一個objc程序啓動後,須要進行類的初始化、調用方法時的cache初始化,再發送消息的時候就直接走緩存(引伸:+load方法和+initialize方法。load方法是首次加載類時調用,絕對只調用一次;initialize方法是首次給類發消息時調用,一般只調用一次,但若是它的子類初始化時未定義initialize方法,則會再調用一次它的initialize方法)。
      struct objc_cache {
      // 緩存bucket的總數
      unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    
      // 實際緩存bucket的總數
      unsigned int occupied                                    OBJC2_UNAVAILABLE;
      // 指向Method數據結構指針的數組
      Method buckets[1]                                        OBJC2_UNAVAILABLE;
      };
    
    複製代碼
  • 操做函數

method_:
invoke: 方法實現的返回值;
// 調用指定方法的實現
id method_invoke ( id receiver, Method m, ... );

// 調用返回一個數據結構的方法的實現
void method_invoke_stret ( id receiver, Method m, ... );
get: 方法名;方法實現;參數與返回值相關;
// 獲取方法名
SEL method_getName ( Method m );

// 返回方法的實現
IMP method_getImplementation ( Method m );
// 獲取描述方法參數和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );
// 返回方法的參數的個數
unsigned int method_getNumberOfArguments ( Method m );
// 經過引用返回方法指定位置參數的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
copy: 返回值類型,參數類型
// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );

// 獲取方法的指定位置參數的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );

// 經過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
set:方法實現;
// 設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );
exchange:交換方法實現
// 交換兩個方法的實現
void method_exchangeImplementations ( Method m1, Method m2 );
description : 方法描述
// 返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );
sel_
// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );

// 在Objective-C Runtime系統中註冊一個方法,將方法名映射到一個選擇器,並返回這個選擇器
SEL sel_registerName ( const char *str );

// 在Objective-C Runtime系統中註冊一個方法
SEL sel_getUid ( const char *str );

// 比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
c、方法調用流程:向對象發送消息,其實是調用objc_msgSend函數,obj_msgSend的實際動做就是:找到這個函數指針,而後調用它。
id objc_msgSend(receiver self, selector _cmd, arg1, arg2, ...)
self和_cmd是隱藏參數,在編譯期被插入實現代碼。
self:指向消息的接受者target的對象類型,做爲一個佔位參數,消息傳遞成功後self將指向消息的receiver。
_cmd: 指向方法實現的SEL類型。
當向通常對象發送消息時,調用objc_msgSend;當向super發送消息時,調用的是objc_msgSendSuper; 若是返回值是一個結構體,則會調用objc_msgSend_stret或objc_msgSendSuper_stret。
0.1-檢查target是否爲nil。若是爲nil,直接cleanup,而後return。(這就是咱們能夠向nil發送消息的緣由。) 若是方法返回值是一個對象,那麼發送給nil的消息將返回nil;若是方法返回值爲指針類型,其指針大小爲小於或者等於sizeof(void*),float,double,long double 或者long long的整型標量,發送給nil的消息將返回0;若是方法返回值爲結構體,發送給nil的消息將返回0。結構體中各個字段的值將都是0;若是方法的返回值不是上述提到的幾種狀況,那麼發送給nil的消息的返回值將是未定義的。 0.2-若是target非nil,在target的Class中根據Selector去找IMP。(由於同一個方法可能在不一樣的類中有不一樣的實現,因此咱們須要依賴於接收者的類來找到的確切的實現)。
1-首先它找到selector對應的方法實現: 
*1.1-在target類的方法緩存列表裏檢查有沒有對應的方法實現,有的話,直接調用。 *1.2-比較請求的selector和類方法列表中的selector,對應的話,直接調用。 
*1.3-比較請求的selector和父類方法列表,父類的父類,直至根類,若是有對應,則直接調用。(方法重寫攔截父類方法的原理) 
2-調用方法實現,並將接收者對象及方法的全部參數傳給它。 
3-最後,將實現函數的返回值做爲本身的返回值。
複製代碼
  • 動態方法解析與消息轉發:若是以上的類中沒有找到對應的selector(通常保險起見先用respondsToSelector:內省判斷):,還能夠利用消息轉發機制依次執行如下流程:
Method Resolution(動態方法解析):
用所屬類的類方法+(BOOL)resolveInstanceMethod:(實例方法)或者+(BOOL)resolveClassMethod:(類方法),在此方法裏添加class_addMethod函數。通常用於@dynamic動態屬性。(當一個屬性聲明爲@dynamic,就是向編譯器保證編譯時不用管/get實現,必定會在運行時實現)。
Fast Forwarding (快速消息轉發):
若是上一步沒法響應消息,調用- (id)forwardingTargetForSelector:(SEL)aSelector方法,將消息接受者轉發到另外一個對象target(不能爲self,不然死循環)。
Normal Forwarding(普通消息轉發):
若是上一步沒法響應消息:
調用方法簽名- (NSMethodSignature )methodSignatureForSelector:(SEL)aSelector,方法簽名目的將函數的參數類型和返回值封裝;
若是返回非nil,則建立一個NSInvocation對象利用方法簽名和selector封裝未被處理的消息,做爲參數傳遞給- (void)forwardInvocation:(NSInvocation )anInvocation。
這一步比較耗時。
若是以上步驟(消息傳遞和消息轉發)仍是不能響應消息,則調動doesNotRecognizeSelector:方法,拋出異常。
複製代碼
  • 協議相關:@protocol聲明瞭能夠被其餘任何類實現的方法,協議僅僅是定義一個接口,而由其餘的類去負責實現。 protocol是一個對象結構體。
objc_:
// 返回指定的協議
Protocol * objc_getProtocol ( const char *name );

// 獲取運行時所知道的全部協議的數組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );

// 建立新的協議實例
Protocol * objc_allocateProtocol ( const char *name );

// 在運行時中註冊新建立的協議
void objc_registerProtocol ( Protocol *proto );
protocol_:
get: 協議;屬性;
// 返回協議名
const char * protocol_getName ( Protocol *p );
// 獲取協議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
copy:協議列表;屬性列表;
// 獲取協議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協議採用的協議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
add:屬性;方法;協議;
// 爲協議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 添加一個已註冊的協議到協議中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );

// 爲協議添加屬性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
isEqual:判斷兩協議等同;
// 測試兩個協議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
comform:判斷是否遵循協議;
// 查看協議是否採用了另外一個協議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
複製代碼
  • 動態實現
Method Swizzling;
Method Swizzling能夠在運行時經過修改類的方法列表中selector對應的函數或者設置交換方法實現,來動態修改方法。能夠重寫某個方法而不用繼承,同時還能夠調用原先的實現。一般應用於在category中添加一個方法。
爲保證改變方法引發衝突,確保方法混用只能一次性:
好比,在+load方法或者dispatch_once中執行。
ISA Swizzling;
ISA Swizzling能夠動態修改對象的isa指針,改變對象的類,相似於建立子類實現相同的功能。KVO便是同過ISA Swizzling實現的。

複製代碼
  • 其餘概念:category、super
category:
typedef struct objc_category *Category;

struct objc_category {
    char *category_name                          OBJC2_UNAVAILABLE; // 分類名
    char *class_name                             OBJC2_UNAVAILABLE; // 分類所屬的類名
    struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 實例方法列表
    struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 類方法列表
    struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分類所實現的協議列表
}  

// objc-runtime-new.h中定義:
struct category_t {
    const char *name;                        // name 是指 class_name 而不是 category_name
    classref_t cls;                          // cls是要擴展的類對象,編譯期間是不會定義的,而是在Runtime階段經過name對應到對應的類對象
    struct method_list_t *instanceMethods;       
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;    // instanceProperties表示Category裏全部的properties,(這就是咱們能夠經過objc_setAssociatedObject和objc_getAssociatedObject增長實例變量的緣由,)不過這個和通常的實例變量是不同的

};
category是定義方法的結構體,instance_methods列表是objc_class中方法列表的一個子集,class_methods列表是元類方法列表的一個子集。由其結構成員可知,category爲何不能添加成員變量(可添加屬性,只有set/get方法)。
給category添加方法後,category_list會生成method list。這個方法列表是倒序添加的,也就是說,新生成的category的方法會先於舊的category的方法插入。(category的方法會優先於類方法執行)。
super:
super並非隱藏參數,它實際上只是一個」編譯器標示符」,它負責告訴編譯器,當調用方法時,跳過當前類去調用父類的方法,而不是本類中的方法。self是類的一個隱藏參數,每一個方法的實現的第一個參數即爲self。實際上給super發消息時,super仍是與self指向的是相同的消息接收者。
struct objc_super {
   __unsafe_unretained id receiver;
   __unsafe_unretained Class super_class;
};
原理:使用super來接收消息時,編譯器會生成一個objc_super結構體。發送消息時,不是調用objc_msgSend函數,而是調用objc_msgSendSuper函數:(這就是爲何我們在super init 返回的是本類)
id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );
該函數實際的操做是:從objc_super結構體指向的superClass的方法列表開始查找selector,找到後以objc->receiver去調用這個selector。
複製代碼
  • RunTime對一些方法的實現
- (Class)class ;

- (Class)class {
    return object_getClass(self);
}

+ (Class)class;

+ (Class)class {
    return self;
}

- (BOOL)isKindOf:aClass;// (for循環遍歷父類,每次判斷返回的結果可能不一樣)

- (BOOL)isKindOf:aClass
{
    Class cls;
    for (cls = isa; cls; cls = cls->superclass) 
        if (cls == (Class)aClass)
            return YES;
    return NO;
}
- (BOOL)isMemberOf:aClass;

- (BOOL)isMemberOf:aClass
{
    return isa == (Class)aClass;
}
複製代碼
相關文章
相關標籤/搜索