Method Swizzling已經被聊爛了,都知道這是Objective-C的黑魔法,能夠交換兩個方法的實現。今天我也來聊一下Method Swizzling。objective-c
咱們先貼上這一大把代碼吧ui
@interface UIViewController (Swizzling) @end @implementation UIViewController (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzling_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Method Swizzling - (void)swizzling_viewWillAppear:(BOOL)animated { [self swizzling_viewWillAppear:animated]; NSLog(@"==%@",NSStringFromClass([self class])); } @end
好的,上面就是Method Swizzling的使用方法,將方法- (void)swizzling_viewWillAppear:(BOOL)animated
和系統級方法- (void)viewWillAppear:(BOOL)animated
交換。經常使用的場景就是埋點,這個咱就不細說了。spa
這裏咱們說一個點就是實現代碼的位置問題。code
咱們的交換代碼必須只能調用一次,若是執行屢次的,那不就把交換的實現又換回來了嗎,因此咱們必須找一個只會調用一次的地方來寫實現交換的代碼。blog
咱們都知道+load
會在加載類的時候執行,並且只執行一次,可是爲了進一步保障他只能執行一次,咱們使用了dispatch_once(由於會有人手動調用+load方法)。繼承
此外+load
方法還有一個很是重要的特性,那就是子類、父類和分類中的+load
方法的實現是被區別對待的。換句話說在 Objective-C runtime 自動調用+load
方法時,分類中的+load
方法並不會對主類中的+load
方法形成覆蓋。get
在取到了SEL和Method以後,執行了下面這句代碼源碼
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
而後根據success來作不一樣的處理。io
這裏咱們先說結論,就拿咱們上面的代碼做爲例子。table
若是咱們的主類,也就是UIViewController(咱們是給UIViewController建立的Category)實現了viewWillAppear:
方法,success爲NO,若是沒有實現,則爲YES。在咱們的例子中的UIViewController確定是實現了viewWillAppear:
方法的,因此success確定爲NO。
若是咱們這裏給一個自定義的VC建立Category實現Swizzling,而且VC沒有顯式的實現viewWillAppear:
(繼承父類的),這時success就是YES了。
你們能夠本身建立不一樣類和類別的試一試。
咱們經過runtime的源碼來看一下class_addMethod
內部作了什麼操做。
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return NO; mutex_locker_t lock(runtimeLock); return ! addMethod(cls, name, imp, types ?: "", NO); }
class_addMethod
返回值是對addMethod
返回值取反。這個地方稍微有點彆扭。咱們能夠看一下addMethod
方法,返回值是IMP,因此說:
主類實現了被Swizzling的方法,success爲NO,即class_addMethod返回NO,addMethod返回值不爲空。
主類未實現了被Swizzling的方法,success爲YES,即class_addMethod返回YES,addMethod返回值爲空。
// addMethod方法的主要代碼 method_t *m; if ((m = getMethodNoSuper_nolock(cls, name))) { // already exists if (!replace) {//replace==NO (class_addMethod) result = m->imp; } else {//(class_replaceMethod) result = _method_setImplementation(cls, m, imp); } } else { // fixme optimize method_list_t *newlist; newlist = (method_list_t *)calloc(sizeof(*newlist), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(method_t) | fixed_up_method_list; newlist->count = 1; newlist->first.name = name; newlist->first.types = strdupIfMutable(types); newlist->first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); cls->data()->methods.attachLists(&newlist, 1); flushCaches(cls); result = nil; }
咱們結合源碼來梳理上面提到的兩種狀況。
首先method_t其實就是一個儲存方法的細節的結構體。
經過m = getMethodNoSuper_nolock(cls, name)
查找類中對應的方法的信息,包括方法名,實現等等。
調用m = getMethodNoSuper_nolock(cls, name)
查找,這裏應該是能夠找到的,class_addMethod
方法中調用addMethod
的時候relpace傳的是NO,因此會執行result = m->imp;
,其實就是給result賦了個值,讓他不爲空。class_addMethod
的返回值是addMethod
返回值取反,因此此時class_addMethod
返回爲NO。
繼續往下執行method_exchangeImplementations(originalMethod, swizzledMethod);
,這就很好理解了,就是直接把須要交換的兩個方法的實現直接交換。
getMethodNoSuper_nolock
找不到,m仍是爲空,因此會執行else下面的代碼,這裏面的代碼其實很明顯,就是動態的爲咱們的主類建立實現了須要被swizzling的方法,固然了,由於此時咱們傳入class_addMethod
方法的sel是須要被swizzling的方法,可是實現已是傳了須要替換後的實現,因此執行完else裏面的代碼以後,咱們的須要被swizzling的方法的實現,已經指向了替換後的實現,也就是viewWillAppear:
的IMP其實此時已經指向了swizzling_viewWillAppear:
的IMP。
最後result置爲nil,因此class_addMethod
返回值就是YES,success爲YES。
最後再執行class_replaceMethod
方法。
從源碼中來看,class_replaceMethod
和class_addMethod
都是調用了addMethod
,但有兩點不一樣,一來是class_replaceMethod
在調用addMethod
時,參數replace傳YES,再者就是由於class_replaceMethod
的返回值是一個IMP,因此和addMethod
是一致的,直接return了addMethod
的返回值。
在經過class_replaceMethod
調用addMethod
的時候,雖然咱們主類以前沒實現要被swizzling的方法,可是在上一步中,咱們已經動態的添加了,因此此時getMethodNoSuper_nolock
是能找到的。
最終執行result = _method_setImplementation(cls, m, imp);
。_method_setImplementation
內部實現很簡單,先用一個old記錄下m->imp 而後再把m->imp設置爲傳入的imp,隨後返回old,其實也就是返回了沒交換前的m->imp。
這樣咱們就經過_method_setImplementation
方法把咱們的swizzling_viewWillAppear:
的IMP指向了viewWillAppear:
的IMP。完成了viewWillAppear:
和swizzling_viewWillAppear:
兩個方法實現的交換。