Objective-C Method Swizzling

Objective-C 中的 Method Swizzling 是一種可以在程序運行時,修改方法調用的技術。是 OC 作爲動態語言的典型證明。

Method Swizzling 是 OC <objc/runtime.h> 類庫提供的“黑魔法”之一。

例子

以替換 NSArray 的 lastObject 方法爲例:

  • 在 NSArray 中添加需要替換 lastObject 的方法 – xxx_lastObject方法:

#import "NSArray+Swizzle.h"  

@implementation NSArray (Swizzle)

- (id)xxx_lastObject
{
    id ret = [self xxx_lastObject];
    NSLog(@"**********  myLastObject ***********");
    return ret;
}
@end


注意這裏的寫法,xxx_lastObject 方法的方法體中調用了 [self xxx_lastObject],這樣寫並不會造成遞歸,後面會交換 xxx_lastObject 與 lastObject 的 IMP,其實 [self xxx_lastObject] 將會執行 [self lastObject] 。

  • 調換 IMP

#import <objc/runtime.h>  
#import "NSArray+Swizzle.h"

Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(xxx_lastObject));
method_exchangeImplementations(ori_Method, my_Method);

注意這裏需要引入 <objc/runtime.h>,否則會報錯,因爲使用了 runtime 中的 method_exchangeImplementations 方法。

使用 method_exchangeImplementations 交換了 xxx_lastObject 與 lastObject 的 IMP。後面會講解更深層次的原理。

  • 嘗試調用 lastObject

#import <objc/runtime.h>
#import "NSArray+Swizzle.h"

NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);

輸出結果爲:

1
2
**********  myLastObject ***********
TEST RESULT : 3

很明顯,這裏調用 lastObject 方法,其實是調用了我們添加的 xxx_lastObject 方法。

用處

Method Swizzling 非常強大,主要作用有:

  • 在不修改 iOS 系統類庫或第三方類庫的源碼基礎上,修改原有調用邏輯
  • 動態添加、修改方法,修復線上 bug(如果 Apple 官方允許的話)

常用 API

相關常用方法,都在<objc/runtime.h>包內:

//向類中添加Method
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

//修改類的Method
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

//交換2個方法中的IMP
void method_exchangeImplementations(Method m1, Method m2)

//獲取類的某個實例方法
Method class_getInstanceMethod(Class aClass, SEL aSelector);

底層原理

在運行時,OC 的方法是一種叫 Method 的結構體,這種 objc_method 類型的結構體定義爲:

struct objc_method
   SEL method_name         OBJC2_UNAVAILABLE;
   char *method_types      OBJC2_UNAVAILABLE;
   IMP method_imp          OBJC2_UNAVAILABLE;
}

  • method_name 是方法的 selector,可以理解爲運行時的方法名;
  • *method_types 是一個參數和返回值類型編碼的字符串;
  • method_imp 是指向方法實現的指針。

Method Swizzling 的實質是在運行時,訪問對象的方法結構體,並改變它的底層實現。

我們以上節修改 NSArray 的 lastObject 爲例做說明。

  • 初始時,lastObject 與 xxx_lastObject 方法的 Method 結構體:
  • Method lastObject {
          SEL method_name = @selector(lastObject)
          char *method_types = “v@:“ //返回值void, 參數id(self),selector(_cmd)
          IMP method_imp = 0x000FFFF //指向([NSArray lastObject])
    }
    
    Method xxx_lastObject {
           SEL method_name = @selector(swizzle_originalMethodName)
           char *method_types = “v@:”
           IMP method_imp = 0x1234AABA //指向([NSArray xxx_lastObject])
    }

  • 調用 void method_exchangeImplementations(Method m1, Method m2) ,交換兩者的實現:
  • Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));
    Method my_Method = class_getInstanceMethod([NSArray class], @selector(xxx_lastObject));
    method_exchangeImplementations(ori_Method, my_Method);


  • 調換後的 Method 結構體:

Method lastObject {
      SEL method_name = @selector(lastObject)
      char *method_types = “@@:“ //返回值id, 參數id(self),selector(_cmd)
      IMP method_imp = 0x1234AABA //指向([NSArray xxx_lastObject])
}

Method xxx_lastObject {
       SEL method_name = @selector(swizzle_originalMethodName)
       char *method_types = “@@:”
       IMP method_imp = 0x000FFFF //指向([NSArray lastObject])
}

可以看到使用void method_exchangeImplementations(Method m1, Method m2)的實質是交換了xxx_lastObject 與 lastObject 的 IMP,實現了在運行時做方法的替換。使得當執行 [array lastObject] 的時候,實際會去執行 [array xxx_lastObject] 的方法實現。

其他提示

+load

Swizzling 的處理,在類的 +load 方法中完成。

因爲 +load 方法會在類被添加到 OC 運行時執行,且只會被調用一次,保證了 Swizzling 方法的及時處理。

dispatch_once

Swizzling 的處理,dispatch_once 中完成。保證只執行一次。

prefix

Swizzling 方法添加前綴,避免方法名稱衝突。

代碼示例

代碼示例

參考資料

Method Swizzling

How to swizzle a class method on iOS?

iOS 4.3: imp_implementationWithBlock()

Objective-C的hook方案(一): Method Swizzling

Objective-C 的動態提示和技巧

文章出自:http://sjpsega.com/blog/2014/09/17/oc-method-swizzling/

發佈了54 篇原創文章 · 獲贊 10 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章