說說objcRuntime的一些妙用(class_addMethod,class_replaceMethod)

前言:陳列一下今天要講的知識點:class_addMethod,class_replaceMethod,method_getImplementation,object_getClass

涉及到的知識

》》使用category,通過Runtime實現用自己的函數調換掉原生函數

》》oc的message forwarding

》》使用Runtime爲類添加原來沒有的方法

》》爲什麼category裏不重寫方法


註明:本文章內技術參考當然來自四面八方,來自不同時期,小弟只是做個總結,有不好的地方歡迎大家指導


先從一個場景問題帶出吧,畢業設計的時候小弟做ipad應用,到後面才決定加上旋轉屏適配,看着100多個文件20多個頁面差點沒把血吐出來,哈哈每個controller去修改方法是不可能的了,因爲強迫症也不想多創個父類,好吧決定一次過替換掉這些controller裏的viewWillAppear:  和  willAnimateRotationToInterfaceOrientation:duration:,換成自己的。


先看一個category

通過運用class_addMethod和class_replaceMethod來調換掉系統庫裏的方法

[objc] view plain copy
  1. #import "NSObject+Swizzle.h"  
  2.   
  3. @implementation NSObject (Swizzle)  
  4.   
  5. + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {  
  6.       
  7.     Method originMethod = class_getInstanceMethod(self, origSel);  
  8.     Method newMethod = class_getInstanceMethod(self, aftSel);  
  9.       
  10.     if(originMethod && newMethod) {//必須兩個Method都要拿到  
  11.         if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {  
  12.             //實現成功添加後  
  13.             class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));  
  14.         }  
  15.         return YES;  
  16.     }  
  17.     return NO;  
  18. }  
  19. @end  
1.傳入兩個參數,原方法選擇子,新方法選擇子,並通過class_getInstanceMethod()拿到對應的Method

2.class_addMethod,是相對於實現來的說的,將本來不存在於被操作的Class裏的newMethod的實現添加在被操作的Class裏,並使用origSel作爲其選擇子(注意參數中的self爲被操作的Class,不要忘了這裏是類方法).

3.class_replaceMethod,addMethod成功完成後,從參數可以看出,目的是換掉method_getImplaementation(roiginMethod)的選擇子,將原方法的實現的SEL換成新方法的SEL:aftSel,ok目的達成了。想一想,現在通過舊方法SEL來調用,就會實現新方法的IMP,通過新方法的SEL來調用,就會實現舊方法的IMP,好了理一理思路繼續往下。



這次就用NSString做載體來演示吧:

[objc] view plain copy
  1. #import "MyString.h"  
  2. #import "NSObject+Swizzle.h"  
  3.   
  4. @implementation MyString  
  5.   
  6. + (void)load {  
  7.     static dispatch_once_t onceToken;  
  8.     dispatch_once(&onceToken, ^{  
  9.         Class clazz = object_getClass((id)self);  
  10.         [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];  
  11.     });  
  12. }  
  13.   
  14. + (BOOL)myResolveInstanceMethod:(SEL)sel {  
  15.       
  16.     if(! [self myResolveInstanceMethod:sel]) {  
  17.         NSString *selString = NSStringFromSelector(sel);  
  18.         if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {  
  19.             class_addMethod(self, sel, class_getMethodImplementation(self@selector(dynamicMethodIMP)), "v@:");  
  20.             return YES;  
  21.         }else {  
  22.             return NO;  
  23.         }  
  24.     }  
  25.     return YES;  
  26. }  
  27.   
  28. - (void)dynamicMethodIMP {  
  29.     NSLog(@"我是動態加入的函數");  
  30. }  
  31.   
  32. @end  

1.首先這裏要提下resolveInstanceMethod:,不瞭解的朋友可以去補一下oc的message forwarding,就是當運行時對象調用了一個找不到的方法的時候系統會去尋找的機制,這個方法是第一步去到的地方,我們可以在這裏面runtime添加方法,是的,首先我們得劫持了這個方法,做我們自己的事,通過剛纔category裏封裝好的swizzleMethod:withMethod:

-------這個時候有朋友有疑問了,我們可以重寫這個方法來做自己的事情啊,其實並不可以,在category裏重寫現有方法會有警告#Category is implementing a method which will also be implemented by its primary class,這種做法是不提倡的!

------------category沒有辦法去代替子類,它不能像子類一樣通過super去調用父類的方法實現。如果category中重寫覆蓋了當前類中的某個方法,那麼這個當前類中的原始方法實現,將永遠不會被執行,這在某些方法裏是致命的(這裏提一下一個特例+(void)load,它會在當前方法裏執行完再去category裏執行).

------------如果兩個category重寫了同一個方法,我們無法控制哪個優先級更高,一直以來還是提倡通過繼承去重寫方法

2.object_getClass拿到當前MyString的Class,調用剛纔category裏封裝好的swizzleMethod:withMethod:,用我們自己的myResolveInstanceMethod:去替換原生的,好了,現在如果我們在運行時調用了一個不存在的方法,系統會去調用我們的myResolveInstanceMethod:,是的不用懷疑。

3.現在看看myResolveInstanceMethod:裏面又調用了一次myResolveInstanceMethod:,有的朋友會以爲是遞歸其實並不是,系統去調用原生的方法,會跑到我們自己的方法實現,是因爲我們之前的swizzle操作沒問題,而不要忘記了,我們自己的方法selector對應的實現,已經換成了原生方法的實現,ok。。if(! [self myResolveInstanceMethod:sel])是調用原生方法的實現,去檢測一次傳入的方法是否存在,如果還是沒有,則做class_addMethod操作爲此類添加對應的方法,return YES,該方法被系統調用,OK,達到目的。

-------這裏補充一個知識點class_addMethod參數的意義,按順序是,類--選擇子--實現--方法的返回值和參數資料。v代表返回值void,@代表id類型對象,:代表選擇子。爲什麼呢,其實每一個oc方法都有兩個隱式的參數(id self, SEL _cmd)也可以說是由C語言函數再加着兩個參數組成一個oc方法。ok,疑問又解決了。


最後看看我們的工作的收穫:

[objc] view plain copy
  1. NSLog(@"begin test");  
  2. //---------------------------------------------------------------  
  3.       
  4.     MyString *string = [[MyString alloc] init];  
  5.     [string performSelector:@selector(countAll)];  
  6.     [string performSelector:@selector(pushViewController)];  
[objc] view plain copy
  1. <pre name="code" class="objc">//---------------------------------------------------------------  
  2.     NSLog(@"finish test");  


-----Log:

[javascript] view plain copy
  1. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test  
  2. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
  3. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
  4. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
  5. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章