Runtime && Method Swizzling

一、Method Swizzling通用方法封裝

能夠將Method Swizzling功能封裝爲類方法,做爲NSObject的類別安全

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSObject (Swizzling) 

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                         bySwizzledSelector:(SEL)swizzledSelector;
@end


import "NSObject+Swizzling.h"
@implementation NSObject (Swizzling)

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    //替換原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    //先嚐試給源SEL添加IMP,這裏是爲了不源SEL沒有實現IMP的狀況
    BOOL didAddMethod = class_addMethod(class,originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        //添加成功:說明源SEL沒有實現IMP,將源SEL的IMP替換到交換SEL的IMP
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
      //添加失敗:說明源SEL已經有IMP,直接將兩個SEL的IMP交換便可
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end

2.SEL、Method、IMP的含義及區別

在運行時,類(Class)維護了一個消息分發列表來解決消息的正確發送。每個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對,其中鍵是這個方法的名字(SEL),值是指向這個方法實現的函數指針 implementation(IMP)多線程

僞代碼表示:
Class {
      MethodList (
                  Method{
                      SEL:IMP;
                  }
                  Method{
                      SEL:IMP;
                  }
                  );
      };

Method Swizzling就是改變類的消息分發列表來讓消息解析時從一個選擇器(SEL)對應到另一個的實現(IMP),同時將原始的方法實現混淆到一個新的選擇器(SEL)。ide

3.爲何要添加didAddMethod判斷函數

 

先嚐試添加原SEL實際上是爲了作一層保護,由於若是這個類沒有實現originalSelector,但其父類實現了,那class_getInstanceMethod會返回父類的方法。這樣method_exchangeImplementations替換的是父類的那個方法,這固然不是咱們想要的。因此咱們先嚐試添加 orginalSelector,若是已經存在,再用 method_exchangeImplementations 把原方法的實現跟新的方法實現給交換掉。
若是理解還不夠透徹,咱們能夠進入runtime.h中查看class_addMethod源碼解釋this

/** 
* Adds a new method to a class with a given name and implementation.
* 
* @param cls The class to which to add a method.
* @param name A selector that specifies the name of the method being added.
* @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
* @param types An array of characters that describe the types of the arguments to the method. 
* 
* @return YES if the method was added successfully, otherwise NO 
*  (for example, the class already contains a method implementation with that name).
*
* @note class_addMethod will add an override of a superclass's implementation, 
*  but will not replace an existing implementation in this class. 
*  To change an existing implementation, use method_setImplementation.
*/

         大概的意思就是咱們能夠經過class_addMethod爲一個類添加方法(包括方法名稱(SEL)和方法的實現(IMP)),返回值爲BOOL類型,表示方法是否成功添加。spa

        須要注意的地方是class_addMethod會添加一個覆蓋父類的實現,但不會取代原有類的實現。也就是說若是class_addMethod返回YES,說明子類中沒有方法originalSelector,經過class_addMethod爲其添加了方法originalSelector,並使其實現(IMP)爲咱們想要替換的實現線程

class_addMethod(class,originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

同時再將原有的實現(IMP)替換到swizzledMethod方法上指針

class_replaceMethod(class,swizzledSelector,
                          method_getImplementation(originalMethod),
                          method_getTypeEncoding(originalMethod));

從而實現了方法的交換,而且未影響父類方法的實現。反之若是class_addMethod返回NO,說明子類中自己就具備方法originalSelector的實現,直接調用交換便可code

method_exchangeImplementations(originalMethod, swizzledMethod);

 

2.方法交換調用在+load方法中

在Objective-C runtime會自動調用兩個類方法,分別爲+load與+ initialize。
  +load 方法是在類被加載的時候調用的,也就是必定會被調用。
  +initialize方法是在類或它的子類收到第一條消息以前被調用的,這裏所指的消息包括實例方法和類方法的調用。也就是說+initialize方法是以懶加載的方式被調用的,若是程序一直沒有給某個類或它的子類發送消息,那麼這個類的+initialize方法是永遠不會被調用的。
  此外+load方法還有一個很是重要的特性,那就是子類、父類和分類中的+load方法的實現是被區別對待的。換句話說在 Objective-C runtime自動調用+load方法時,分類中的+load方法並不會對主類中的+load方法形成覆蓋。綜上所述,+load 方法是實現 Method Swizzling 邏輯的最佳「場所」

  詳細請看:http://www.jianshu.com/p/872447c6dc3f

3.方法交換要在dispatch_once中執行

方法交換應該要線程安全,並且保證在任何狀況下(多線程環境,或者被其餘人手動再次調用+load方法)只交換一次,防止再次調用又將方法交換回來。除非只是臨時交換使用,在使用完成後又交換回來。 最經常使用的解決方案是在+load方法中使用dispatch_once來保證交換是安全的。以前有讀者反饋+load方法自己即爲線程安全,爲何仍需添加dispatch_once,其緣由就在於+load方法自己沒法保證其中代碼只被執行一次
相關文章
相關標籤/搜索