Runtime中的Method Swizzling

runtime中的Method Swizzling(AOP)

一、動態的為類注入新的方法

使用場景:攔截系統方法 eg: viewVillAppear,viewDidLoad,imageNamed....
使用到的方法

- 添加方法
/**
 * cls 被添加方法所在的類
 * name 添加的方法名字
 * IMP    實現這個方法的函數
 * types  一個定義函數返回類型和參數的類型的字符串 (常用method_getTypeEncoding(method)獲取)
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

- 替換方法的實現
class_replaceMethod(Class cls, SEL name, IMP imp,  const char *types) 

- 交換兩個方法的實現
method_exchangeImplementations(Method m1, Method m2)     

- 獲得某個類的類方法
Method class_getClassMethod(Class cls , SEL name)

- 獲得某個類的實例對象方法
Method class_getInstanceMethod(Class cls , SEL name) 
                    

其本質無非就是改變方法選擇器指針的指向(改變指針的地址)
eg:AOP中攔截一個方法的實現

// originalSelector  原有方法
// swizzledSelector 新的方法
+ (void)swizzledMethod:(SEL)originalSelector and:(SEL)swizzledSelector
{
  // 獲取當前類
  Class currentClass = [self class];
  // 當前方法選擇器指向的實例方法
  Method originalMethod = class_getInstanceMethod(currentClass, originalSelector);
  Method swizzledMethod = class_getInstanceMethod(currentClass, swizzledSelector);
  
  // 為當前類添加一個方法(如果當前類中已經存在originalSelector,返回false,否則返回true)
  BOOL didAddMethod =
  class_addMethod(currentClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  // 方法添加成功
  if (didAddMethod) {
      class_replaceMethod(currentClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  } else {// 如果當前類中已經存在originalSelector 直接交換兩個方法的實現
      method_exchangeImplementations(originalMethod, swizzledMethod);
  }
}

選擇器、方法與實現

680076-b00b5b0a1a633428.png

在Objective-C中,選擇器(selector)、方法(method)和實現(implementation)是運行時中一個特殊點,雖然在一般情況下,這些術語更多的是用在消息發送的過程描述中。

以下是Objective-C Runtime Reference中的對這幾個術語一些描述:

Selector(typedef struct objc_selector *SEL):用于在運行時中表示一個方法的名稱。一個方法選擇器是一個C字符串,它是在Objective-C運行時被注冊的。選擇器由編譯器生成,并且在類被加載時由運行時自動做映射操作。
Method(typedef struct objc_method Method):在類定義中表示方法的類型
Implementation(typedef id (
IMP)(id, SEL, …)):這是一個指針類型,指向方法實現函數的開始位置。這個函數使用為當前CPU架構實現的標準C調用規范。每一個參數是指向對象自身的指針(self),第二個參數是方法選擇器。然后是方法的實際參數。
理解這幾個術語之間的關系最好的方式是:一個類維護一個運行時可接收的消息分發表;分發表中的每個入口是一個方法(Method),其中key是一個特定名稱,即選擇器(SEL),其對應一個實現(IMP),即指向底層C函數的指針。

為了swizzle一個方法,我們可以在分發表中將一個方法的現有的選擇器映射到不同的實現,而將該選擇器對應的原始實現關聯到一個新的選擇器中。

注意細節
Swizzling應該總是在+load中執行

在Objective-C中,運行時會自動調用每個類的兩個方法。+load會在類初始加載時調用,+initialize會在第一次調用類的類方法或實例方法之前被調用。這兩個方法是可選的,且只有在實現了它們時才會被調用。由于method swizzling會影響到類的全局狀態,因此要盡量避免在并發處理中出現競爭的情況。+load能保證在類的初始化過程中被加載,并保證這種改變應用級別的行為的一致性。相比之下,+initialize在其執行時不提供這種保證—事實上,如果在應用中沒為給這個類發送消息,則它可能永遠不會被調用。

Swizzling應該總是在dispatch_once中執行

與上面相同,因為swizzling會改變全局狀態,所以我們需要在運行時采取一些預防措施。原子性就是這樣一種措施,它確保代碼只被執行一次,不管有多少個線程。GCD的dispatch_once可以確保這種行為,我們應該將其作為method swizzling的最佳實踐。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,774評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,628評論 33 466
  • 繼上Runtime梳理(四) 通過前面的學習,我們了解到Objective-C的動態特性:Objective-C不...
    小名一峰閱讀 770評論 0 3
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,231評論 0 7
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認知 Runtime詳解 應用...
    Ryan___閱讀 1,960評論 1 3