iOS開發 - method swizzle方式的選擇

1 method swizzle方式的選擇

1.1 錯誤的swizzle方式

根據 right-way-to-swizzle 文章的闡述,當我們進行方法交換時,實質是交換了objc_method結構體中的IMP函數指針

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

例如:我們hook一個方法method,這裏叫 originalMethodName,通過method_exchangeImplementations交換其實現IMP

未swizzle前,method的內容如下

 Method m1 { //this is the original method. we want to switch this one with
             //our replacement method
      SEL method_name = @selector(originalMethodName)
      char *method_types = “v@://returns void, params id(self),selector(_cmd)
      IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
 }
 
 Method m2 { //this is the swizzle method. We want this method executed when [MyClass
             //originalMethodName] is called
       SEL method_name = @selector(swizzle_originalMethodName)
       char *method_types = “v@:”
       IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
 }

進行method_exchangeImplementations交換objc_method中的IMP

m1 = class_getInstanceMethod([MyClass class], @selector(originalMethodName));
m2 = class_getInstanceMethod([MyClass class], @selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2)

swizzle method之後,method的內容如下:

 Method m1 { //this is the original Method struct. we want to switch this one with
             //our replacement method
     SEL method_name = @selector(originalMethodName)
     char *method_types = “v@://returns void, params id(self),selector(_cmd)
     IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
 }
 
 Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass
            //originalMethodName] is called
     SEL method_name = @selector(swizzle_originalMethodName)
     char *method_types = “v@:”
     IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
 }

可以看出SELIMP並不匹配,.m文件我們一般會這樣寫

.m我們一般會這樣寫

- (void)originalMethodName {
	// do your logic
}

- (void)swizzle_originalMethodName {
	// hook before action
	[self swizzle_originalMethodName]; //調用原有的originalMethodName方法
	// hook after action
}

即調用swizzle_originalMethodName來觸發原方法的原因是SELIMP並不對應,當方法實現中,使用_cmd來獲取方法名時,就獲取的SEL和預計的不一致,爲了解決這樣的問題,就在方法交換時,不破壞objc_method結構的含義,SEL和IMP對應。

_cmd在Objective-C的方法中表示當前方法(objc_method)的selector

思考:那麼使用__FUNCTION__獲取函數名是否就可以了呢?

對viewDidLoad方法swizzle後同一方法中兩個輸出打印爲:

__FUNCTION__: -[CCRootViewController viewDidLoad_swizzle] SEL:viewDidLoad

__FUNCTION__是可以判斷函數名,而不是取SEL

- (void) originalMethodName //m1
 {
          assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]); //方法交換後,這裏斷言就會攔截掉,不會往下走了,因爲_cmd並不是originalMethodNamed,而是swizzle_originalMethodName
          //method_exchangedImplementations()
          //…
 

1.2 正確的swizzle方式

避免使用Objective-C聲明方法的方式,因爲其會創建一個objc_method結構體,我們僅僅需要替換其IMP,那就創建一個C函數(用於替換IMP)

void __Swizzle_OriginalMethodName(id self, SEL _cmd)
 {
      //code
 }
 
 IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;

使用method_setImplementation(method, swizzleImp);方法來設定其objc_method中的IMP,其返回值會返回原始的IMP

IMP originalImp = method_setImplementation(method,swizzleImp);

原始的IMP我們可以通過函數指針來存儲,這樣在swizzleImp實現中,我們調用這個函數指針,既可以達到了hook的目的。


1.3 案例

原文中的用例貼出:

SwizzleExampleClass.h

@interface SwizzleExampleClass : NSObject
 - (void) swizzleExample;
 - (int) originalMethod;
 @end

SwizzleExampleClass.m

 static IMP __original_Method_Imp;
 int _replacement_Method(id self, SEL _cmd)
 {
      assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
      //code
     int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
    return returnValue + 1;
 }
 
 @implementation SwizzleExampleClass
 
 - (void) swizzleExample //call me to swizzle
 {
     Method m = class_getInstanceMethod([self class],
 @selector(originalMethod));
     __original_Method_Imp = method_setImplementation(m,
 (IMP)_replacement_Method);
 }
 
 - (int) originalMethod
 {
        //code
        assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
        return 1;
 }
@end
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章