說說objcRuntime的一些妙用(class_addMethod,class_replaceMe


前言: 
陳列一下今天要講的知識點:classaddMethod,classreplaceMethod,methodgetImplementation,objectgetClass數組

涉及到的知識 
--使用category,經過Runtime實現用本身的函數調換掉原生函數 
--oc的message forwarding 
--使用Runtime爲類添加原來沒有的方法 
--爲何category裏不重寫方法函數

註明: 
本文章內技術參考固然來自四面八方,來自不一樣時期,小弟只是作個總結,有很差的地方歡迎你們指導spa

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



先看一個category 經過運用classaddMethodclassreplaceMethod來調換掉系統庫裏的方法設計

#import "NSObject+Swizzle.h"@implementation NSObject (Swizzle)+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {

    Method originMethod = class_getInstanceMethod(self, origSel);
    Method newMethod = class_getInstanceMethod(self, aftSel);    if(originMethod && newMethod) {//必須兩個Method都要拿到
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {            //實現成功添加後
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }        return YES;
    }    return NO;
}@end

1.傳入兩個參數,原方法選擇子,新方法選擇子,並經過classgetInstanceMethod()拿到對應的Method 

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

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



此次就用NSString作載體來演示吧:code

#import "MyString.h"#import "NSObject+Swizzle.h"@implementation MyString+ (void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{
        Class clazz = object_getClass((id)self);
        [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
    });
}

+ (BOOL)myResolveInstanceMethod:(SEL)sel {    if(! [self myResolveInstanceMethod:sel]) {        NSString *selString = NSStringFromSelector(sel);        if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
            class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");            return YES;
        }else {            return NO;
        }
    }    return YES;
}

- (void)dynamicMethodIMP {    NSLog(@"我是動態加入的函數");
}@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.objectgetClass拿到當前MyString的Class,調用剛纔category裏封裝好的swizzleMethod:withMethod:,用咱們本身的myResolveInstanceMethod:去替換原生的,好了,如今若是咱們在運行時調用了一個不存在的方法,系統會去調用咱們的myResolveInstanceMethod:,是的不用懷疑。 

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

class_addMethod參數的意義orm

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

按順序是,類--選擇子--實現--方法的返回值和參數資料。 
v表明返回值void,@表明id類型對象,:表明選擇子。 
why? 其實每個oc方法都有兩個隱式的參數(id self, SEL _cmd),也能夠說是由C語言函數再加着兩個參數組成一個oc方法。對象

最後看看咱們的工做的收穫:繼承

NSLog(@"begin test");  
//------------------------------------------------

    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">  
//------------------------------------------------
    NSLog(@"finish test");

-----Log:2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態加入的函數  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test
相關文章
相關標籤/搜索