objcRuntime黑魔法(class_addMethod,class_replaceMethod)

歡迎轉(zhuǎn)載,請(qǐng)注明出處:
http://zyden.vicp.cc/about-objcruntime/
謝謝

前言:
陳列一下今天要講的知識(shí)點(diǎn):class_addMethod,class_replaceMethod,method_getImplementation,object_getClass

涉及到的知識(shí)
--使用category,通過(guò)Runtime實(shí)現(xiàn)用自己的函數(shù)調(diào)換掉原生函數(shù)
--oc的message forwarding
--使用Runtime為類添加原來(lái)沒(méi)有的方法
--為什么category里不重寫方法

注明:
本文章內(nèi)技術(shù)參考當(dāng)然來(lái)自四面八方,來(lái)自不同時(shí)期,小弟只是做個(gè)總結(jié),有不好的地方歡迎大家指導(dǎo)

先從一個(gè)場(chǎng)景問(wèn)題帶出吧,畢業(yè)設(shè)計(jì)的時(shí)候小弟做ipad應(yīng)用,到后面才決定加上旋轉(zhuǎn)屏適配,看著100多個(gè)文件20多個(gè)頁(yè)面差點(diǎn)沒(méi)把血吐出來(lái),哈哈每個(gè)controller去修改方法是不可能的了,因?yàn)閺?qiáng)迫癥也不想多創(chuàng)個(gè)父類,好吧決定一次過(guò)替換掉這些controller里的viewWillAppear: 和 willAnimateRotationToInterfaceOrientation:duration:,換成自己的。




先看一個(gè)category
通過(guò)運(yùn)用class_addMethodclass_replaceMethod來(lái)調(diào)換掉系統(tǒng)庫(kù)里的方法

#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) {//必須兩個(gè)Method都要拿到
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
            //實(shí)現(xiàn)成功添加后
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }
        return YES;
    }
    return NO;
}
@end

1.傳入兩個(gè)參數(shù),原方法選擇子,新方法選擇子,并通過(guò)class_getInstanceMethod()拿到對(duì)應(yīng)的Method


2.class_addMethod,是相對(duì)于實(shí)現(xiàn)來(lái)的說(shuō)的,將本來(lái)不存在于被操作的Class里的newMethod的實(shí)現(xiàn)添加在被操作的Class里,并使用origSel作為其選擇子(注意參數(shù)中的self為被操作的Class,不要忘了這里是類方法).


3.class_replaceMethod,addMethod成功完成后,從參數(shù)可以看出,目的是換掉method_getImplaementation(roiginMethod)的選擇子,將原方法的實(shí)現(xiàn)的SEL換成新方法的SEL:aftSel,ok目的達(dá)成了。想一想,現(xiàn)在通過(guò)舊方法SEL來(lái)調(diào)用,就會(huì)實(shí)現(xiàn)新方法的IMP,通過(guò)新方法的SEL來(lái)調(diào)用,就會(huì)實(shí)現(xiàn)舊方法的IMP,好了理一理思路繼續(xù)往下。




這次就用NSString做載體來(lái)演示吧:

#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(@"我是動(dòng)態(tài)加入的函數(shù)");
}

@end

1.首先這里要提下resolveInstanceMethod:,不了解的朋友可以去補(bǔ)一下oc的message forwarding,就是當(dāng)運(yùn)行時(shí)對(duì)象調(diào)用了一個(gè)找不到的方法的時(shí)候系統(tǒng)會(huì)去尋找的機(jī)制,這個(gè)方法是第一步去到的地方,我們可以在這里面runtime添加方法,是的,首先我們得劫持了這個(gè)方法,做我們自己的事,通過(guò)剛才category里封裝好的swizzleMethod:withMethod:
-------這個(gè)時(shí)候有朋友有疑問(wèn)了,我們可以重寫這個(gè)方法來(lái)做自己的事情啊,其實(shí)并不可以,在category里重寫現(xiàn)有方法會(huì)有警告#Category is implementing a method which will also be implemented by its primary class,這種做法是不提倡的!
------------category沒(méi)有辦法去代替子類,它不能像子類一樣通過(guò)super去調(diào)用父類的方法實(shí)現(xiàn)。如果category中重寫覆蓋了當(dāng)前類中的某個(gè)方法,那么這個(gè)當(dāng)前類中的原始方法實(shí)現(xiàn),將永遠(yuǎn)不會(huì)被執(zhí)行,這在某些方法里是致命的(這里提一下一個(gè)特例+(void)load,它會(huì)在當(dāng)前方法里執(zhí)行完再去category里執(zhí)行).
------------如果兩個(gè)category重寫了同一個(gè)方法,我們無(wú)法控制哪個(gè)優(yōu)先級(jí)更高,一直以來(lái)還是提倡通過(guò)繼承去重寫方法


2.object_getClass拿到當(dāng)前MyString的Class,調(diào)用剛才category里封裝好的swizzleMethod:withMethod:,用我們自己的myResolveInstanceMethod:去替換原生的,好了,現(xiàn)在如果我們?cè)谶\(yùn)行時(shí)調(diào)用了一個(gè)不存在的方法,系統(tǒng)會(huì)去調(diào)用我們的myResolveInstanceMethod:,是的不用懷疑。


3.現(xiàn)在看看myResolveInstanceMethod:里面又調(diào)用了一次myResolveInstanceMethod:,有的朋友會(huì)以為是遞歸其實(shí)并不是,系統(tǒng)去調(diào)用原生的方法,會(huì)跑到我們自己的方法實(shí)現(xiàn),是因?yàn)槲覀冎暗膕wizzle操作沒(méi)問(wèn)題,而不要忘記了,我們自己的方法selector對(duì)應(yīng)的實(shí)現(xiàn),已經(jīng)換成了原生方法的實(shí)現(xiàn),ok。。if(! [self myResolveInstanceMethod:sel])是調(diào)用原生方法的實(shí)現(xiàn),去檢測(cè)一次傳入的方法是否存在,如果還是沒(méi)有,則做class_addMethod操作為此類添加對(duì)應(yīng)的方法,return YES,該方法被系統(tǒng)調(diào)用,OK,達(dá)到目的。


class_addMethod參數(shù)的意義

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

按順序是,類--選擇子--實(shí)現(xiàn)--方法的返回值和參數(shù)資料。
v代表返回值void,@代表id類型對(duì)象,:代表選擇子。
why? 其實(shí)每一個(gè)oc方法都有兩個(gè)隱式的參數(shù)(id self, SEL _cmd),也可以說(shuō)是由C語(yǔ)言函數(shù)再加著兩個(gè)參數(shù)組成一個(gè)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] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,072評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 839評(píng)論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 888評(píng)論 0 1
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 867評(píng)論 0 4
  • 12月21日作業(yè): 1.把今天學(xué)習(xí)好的歸類去做歸類,執(zhí)行! 2.聽(tīng)2個(gè)產(chǎn)品課件記下筆記。 3.增加10個(gè)名單。 新...
    曉風(fēng)姐卓越快樂(lè)閱讀 300評(píng)論 0 0

友情鏈接更多精彩內(nèi)容