(轉(zhuǎn))Objective-C的方法替換

原文地址:http://blog.csdn.net/horkychen/article/details/8532087

本文將要討論Objective-C中的方法替換(method replacement)和swizzling(移魂大法)奔害。

重寫類的方法(Overriding Methods)
Overriding methods在任何面向?qū)ο笳Z言中都很常見祝钢,主要用于子類化中姆吭。在子類中復(fù)寫一個(gè)方法监婶,然后在子類的實(shí)例就可以使用這個(gè)被重寫的方法筛圆。

對(duì)于一個(gè)你無法控制其實(shí)例化(instantiation)的類卡骂,有時(shí)你或許會(huì)想復(fù)寫它的某個(gè)方法崔慧,雖然有點(diǎn)瘋狂狠半。子類化可做不到宰翅,因?yàn)槟銢]有機(jī)會(huì)子類化你的子類一铅。

偽裝(Posing)
Posing是個(gè)很有趣的技術(shù),不過已經(jīng)過時(shí)了堕油,因?yàn)?4位和iPhone環(huán)境下的Objective-C Runtime中不再支持它了. 通過這個(gè)偽裝(posing),你可子類化潘飘,然后將這個(gè)子類偽裝成它的父類。像變魔術(shù)一般掉缺,Runtime會(huì)讓這個(gè)子類應(yīng)用于各處卜录,這時(shí)方法復(fù)寫又有了用處。既然被拋棄了眶明,也就不必多費(fèi)口舌了艰毒。

歸類(Categories)
使用歸類(category)的技術(shù),可以方便地為一個(gè)已經(jīng)存在的類復(fù)寫其方法:
@implementationNSView(MyOverride)

- (void)drawRect: (NSRect)r
{
    *// 這個(gè)會(huì)替換掉通常使用的-[NSView drawRect:]*
    [[NSColor blueColor]set];
    NSRectFill(r);
}
@end

這種方法其實(shí)僅僅適用于復(fù)寫目標(biāo)類的父類中實(shí)現(xiàn)的函數(shù)。如果直接復(fù)寫目標(biāo)類中的方法搜囱,使用歸類會(huì)帶來兩個(gè)問題:
它無法調(diào)用方法的之前的實(shí)現(xiàn)丑瞧。替換掉后,之前的實(shí)現(xiàn)就被完全改寫了蜀肘。但大部分情況下绊汹,只是想增加些功能,并不期望完全替代扮宠。
如果被多個(gè)category復(fù)寫西乖,運(yùn)行時(shí)(runtime)并不保證哪個(gè)真正會(huì)被使用到。

Swizzling (譯為“移魂大法”比較合適,就是太夸張了!)
使用一個(gè)稱為swizzling的技術(shù)获雕,可以為歸類(category)解決上面兩個(gè)問題薄腻,既可以調(diào)用舊的實(shí)現(xiàn),又可以避免多個(gè)category帶來的不確定性届案。它的秘訣是使用一個(gè)不同的函數(shù)名來復(fù)寫庵楷,然后由運(yùn)行時(shí)(runtime)交換它們。

首先楣颠,用一個(gè)不同的名字復(fù)寫:
@implementationNSView(MyOverride)

- (void)override_drawRect: (NSRect)r
{
    *// 調(diào)用舊的實(shí)現(xiàn)尽纽。因?yàn)樗鼈円呀?jīng)被替換了*
    [self override_drawRect: r];
   
    [[NSColor blueColor]set];
    NSRectFill(r);
}
@end

(譯注:呵呵,不知道你是不是和我一樣球碉,初次看到代碼還以為是個(gè)遞歸調(diào)用呢蜓斧。) 其實(shí)是這個(gè)新的方法在執(zhí)行時(shí)已經(jīng)和原先的函數(shù)對(duì)調(diào)了(現(xiàn)在還沒做到仓蛆,往下看U龆)。在運(yùn)行時(shí)看疙,調(diào)用 override_drawRect: 方法其實(shí)就是調(diào)用舊的實(shí)現(xiàn)豆拨。

接下來,你還要寫些代碼才能完成交換:
voidMethodSwizzle(Class c,SEL origSEL,SEL overrideSEL)
{
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod= class_getInstanceMethod(c, overrideSEL);
周全起見能庆,有兩種情況要考慮一下施禾。第一種情況是要復(fù)寫的方法(overridden)并沒有在目標(biāo)類中實(shí)現(xiàn)(notimplemented),而是在其父類中實(shí)現(xiàn)了搁胆。第二種情況是這個(gè)方法已經(jīng)存在于目標(biāo)類中(does existin the class itself)弥搞。這兩種情況要區(qū)別對(duì)待。

(譯注: 這個(gè)地方有點(diǎn)要明確一下渠旁,它的目的是為了使用一個(gè)重寫的方法替換掉原來的方法攀例。但重寫的方法可能是在父類中重寫的,也可能是在子類中重寫的顾腊。)

對(duì)于第一種情況粤铭,應(yīng)當(dāng)先在目標(biāo)類增加一個(gè)新的實(shí)現(xiàn)方法(override),然后將復(fù)寫的方法替換為原先(的實(shí)現(xiàn)(original one)杂靶。

運(yùn)行時(shí)函數(shù)class_addMethod 如果發(fā)現(xiàn)方法已經(jīng)存在梆惯,會(huì)失敗返回,也可以用來做檢查用:
if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod),method_getTypeEncoding(overrideMethod)))
{

如果添加成功(在父類中重寫的方法)吗垮,再把目標(biāo)類中的方法替換為舊有的實(shí)現(xiàn):
class_replaceMethod(c,overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}

(譯注:addMethod會(huì)讓目標(biāo)類的方法指向新的實(shí)現(xiàn)垛吗,使用replaceMethod再將新的方法指向原先的實(shí)現(xiàn),這樣就完成了交換操作烁登。)

如果添加失敗了职烧,就是第二情況(在目標(biāo)類重寫的方法)。這時(shí)可以通過method_exchangeImplementations來完成交換:
else
{
method_exchangeImplementations(origMethod,overrideMethod);
}
}

對(duì)于第二種情況,因?yàn)閏lass_getInstanceMethod 會(huì)返回父類的實(shí)現(xiàn)蚀之,如果直接替換蝗敢,就會(huì)替換掉父類的實(shí)現(xiàn),而不是目標(biāo)類中的實(shí)現(xiàn)足删。(詳細(xì)的函數(shù)說明在這里)

舉個(gè)具體的例子, 假設(shè)要替換掉-[NSView description]. 如果NSView 沒有實(shí)現(xiàn)-description (可選的) 那你就可會(huì)得到NSObject的方法寿谴。如果調(diào)用method_exchangeImplementations , 你就會(huì)把NSObject 的方法替換成你的代碼。這應(yīng)該不會(huì)是你想要的吧失受?

最后在一個(gè)合適位置調(diào)用一下就可以了讶泰。比如在一個(gè)+load 方法中調(diào)用:
+ (void)load
{
MethodSwizzle(self,@selector(drawRect:),@selector(override_drawRect:));
}


直接重寫(Direct Override)
前面的內(nèi)容確實(shí)有些難懂。Swizzling的概念的確顯得有些古怪拂到,特別是在函數(shù)中轉(zhuǎn)來轉(zhuǎn)去的痪署,多少讓人有些思維扭曲的感覺。我下面要介紹一個(gè)更為簡潔兄旬,也更容易理解和實(shí)現(xiàn)的方式狼犯。

這種方式不再需要保存舊有的方法,也不必動(dòng)態(tài)的區(qū)分[self override_drawRect: r] 领铐。我們從頭實(shí)現(xiàn)悯森。

相對(duì)于將原有的方法存放于一個(gè)新的方法中,這里使用一個(gè)全局指針來保存:
void (*gOrigDrawRect)(id,SEL, NSRect);

然后在+load 里賦值:
+ (void)load
{
Method origMethod = class_getInstanceMethod(self,@selector(drawRect:));
gOrigDrawRect = (void*)method_getImplementation(origMethod);
(我喜歡把它轉(zhuǎn)換為 void *绪撵,因?yàn)楸饶切┯珠L又奇怪的函數(shù)指針好輸入多了瓢姻。)

然后像前面介紹的那樣用新的實(shí)現(xiàn)替換掉就可以了。因?yàn)閏lass_replaceMethod本身會(huì)嘗試調(diào)用class_addMethod和method_setImplementation音诈,所以直接調(diào)用class_replaceMethod就可以了幻碱。

實(shí)現(xiàn)如下:
Method origMethod =class_getInstanceMethod(self, @selector(drawRect:)); gOrigDrawRect = (void *)class_replaceMethod(self,@selector(drawRect:), (IMP)OverrideDrawRect,method_getTypeEncoding(origMethod))

最后實(shí)現(xiàn)復(fù)寫方法。和之前不同的是细溅,這里是一個(gè)方法褥傍,而不是方法:
staticvoidOverrideDrawRect(NSView*self,SEL _cmd, NSRect r)
{
gOrigDrawRect(self,_cmd, r);
[[NSColor blueColor]set];
NSRectFill(r);
}
當(dāng)然,這個(gè)方法不是那么優(yōu)雅谒兄,不過我認(rèn)為它更易于運(yùn)用摔桦。

溫馨提示(The Obligatory Warning)
復(fù)寫不是你自家的類是危險(xiǎn)的! 盡量避免這么做承疲,要不然就盡最大的可能細(xì)心處理邻耕。

原文地址: FridayQA,2010-01-29, Method Replacement for Fun and Profit
參考: Objective-CRuntime Reference
轉(zhuǎn)載請(qǐng)注明出處: http://blog.csdn.net/horkychen

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市燕鸽,隨后出現(xiàn)的幾起案子兄世,更是在濱河造成了極大的恐慌,老刑警劉巖啊研,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件御滩,死亡現(xiàn)場離奇詭異鸥拧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)削解,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門富弦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人氛驮,你說我怎么就攤上這事腕柜。” “怎么了矫废?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵盏缤,是天一觀的道長。 經(jīng)常有香客問我蓖扑,道長唉铜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任律杠,我火速辦了婚禮潭流,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俩功。我一直安慰自己幻枉,他們只是感情好碰声,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布诡蜓。 她就那樣靜靜地躺著,像睡著了一般胰挑。 火紅的嫁衣襯著肌膚如雪蔓罚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天瞻颂,我揣著相機(jī)與錄音豺谈,去河邊找鬼。 笑死贡这,一個(gè)胖子當(dāng)著我的面吹牛茬末,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盖矫,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼丽惭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辈双?” 一聲冷哼從身側(cè)響起责掏,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎湃望,沒想到半個(gè)月后换衬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痰驱,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年瞳浦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了担映。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叫潦,死狀恐怖另萤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诅挑,我是刑警寧澤四敞,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站拔妥,受9級(jí)特大地震影響忿危,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜没龙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一铺厨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硬纤,春花似錦解滓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溪王,卻和暖如春腮鞍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莹菱。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工移国, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人道伟。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓迹缀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜜徽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祝懂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 727評(píng)論 0 2
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)娜汁,它使得 Objective-C 如虎添翼嫂易,具備了靈活的...
    lylaut閱讀 792評(píng)論 0 4
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 913評(píng)論 0 6
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言掐禁,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢怜械?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,172評(píng)論 0 7