objcRuntime黑魔法(class_addMethod,class_replaceMethod)

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

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

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

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

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




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

#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ù)涛漂,原方法選擇子赏表,新方法選擇子,并通過class_getInstanceMethod()拿到對應(yīng)的Method


2.class_addMethod,是相對于實(shí)現(xiàn)來的說的匈仗,將本來不存在于被操作的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)在通過舊方法SEL來調(diào)用何鸡,就會實(shí)現(xiàn)新方法的IMP纺弊,通過新方法的SEL來調(diào)用,就會實(shí)現(xiàn)舊方法的IMP骡男,好了理一理思路繼續(xù)往下淆游。




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

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

@end

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


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


3.現(xiàn)在看看myResolveInstanceMethod:里面又調(diào)用了一次myResolveInstanceMethod:令境,有的朋友會以為是遞歸其實(shí)并不是展父,系統(tǒng)去調(diào)用原生的方法返劲,會跑到我們自己的方法實(shí)現(xiàn),是因?yàn)槲覀冎暗膕wizzle操作沒問題吕漂,而不要忘記了惶凝,我們自己的方法selector對應(yīng)的實(shí)現(xiàn),已經(jīng)換成了原生方法的實(shí)現(xiàn)苍鲜,ok混滔。歹颓。if(! [self myResolveInstanceMethod:sel])是調(diào)用原生方法的實(shí)現(xiàn),去檢測一次傳入的方法是否存在领跛,如果還是沒有吠昭,則做class_addMethod操作為此類添加對應(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類型對象恩脂,:代表選擇子趣斤。
why? 其實(shí)每一個(gè)oc方法都有兩個(gè)隱式的參數(shù)(id self, SEL _cmd),也可以說是由C語言函數(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] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盲厌,隨后出現(xiàn)的幾起案子座菠,更是在濱河造成了極大的恐慌,老刑警劉巖拓萌,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異品嚣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)罩旋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浴骂,“玉大人,你說我怎么就攤上這事梯轻。” “怎么了芬为?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵孙乖,是天一觀的道長弯屈。 經(jīng)常有香客問我,道長宴偿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮翻伺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘未妹。我一直安慰自己族檬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布扫尖。 她就那樣靜靜地躺著,像睡著了一般掠廓。 火紅的嫁衣襯著肌膚如雪换怖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天蟀瞧,我揣著相機(jī)與錄音沉颂,去河邊找鬼。 笑死悦污,一個(gè)胖子當(dāng)著我的面吹牛切端,可吹牛的內(nèi)容都是我干的怠益。 我是一名探鬼主播笛谦,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼乳附,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滚停?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤锋叨,失蹤者是張志新(化名)和其女友劉穎豺瘤,沒想到半個(gè)月后派任,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體员辩,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胸遇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罐呼,到底是詐尸還是另有隱情馁痴,我是刑警寧澤杀饵,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站谬擦,受9級特大地震影響切距,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惨远,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一谜悟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锨络,春花似錦赌躺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掠归,卻和暖如春缅叠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虏冻。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工肤粱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厨相。 一個(gè)月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓领曼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛮穿。 傳聞我的和親對象是個(gè)殘疾皇子庶骄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容

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