原文地址: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