上一章Runtime 更多應(yīng)用中開頭就談到了Runtime交叉方法的簡(jiǎn)單使用
這里,來深入討論一些細(xì)節(jié),比如子類的方法替換會(huì)不會(huì)影響父類的方法
創(chuàng)建兩個(gè)類 Vampire、June繼承自Vampire
Vampire中寫一個(gè)方法 -Vampire
June中重寫-Vampire,并新增-JuneSwizzle用于交叉方法替換-Vampire的實(shí)現(xiàn)
June是此文的 目標(biāo)類
注意:其實(shí)如果將方法交換寫的嚴(yán)謹(jǐn)一些,需要三個(gè)運(yùn)行時(shí)函數(shù)class_addMethodclass_replaceMethodmethod_exchangeImplementations(上一章只用了這個(gè))- 創(chuàng)建
Vampire類
- 創(chuàng)建
@interface Vampire : NSObject
- (void)Vampire;
@end
@implementation Vampire
- (void)Vampire
{
NSLog(@" 1 %@ %s",[self class],__func__);
}
@end
- 創(chuàng)建
June類 繼承自Vampire
- 創(chuàng)建
#import "Vampire.h"
@interface June : Vampire
@end
* 引入 runtime.h
#import <objc/runtime.h>
@implementation June
* 此方法來自父類 `Vampire`
- (void)Vampire{
NSLog(@" 2 %@ %s",[self class],__func__);
}
* 用于替換 `-Vampire` 方法實(shí)現(xiàn)
- (void)JuneSwizzle{
NSLog(@" 33 %@ %s",[self class],__func__);
}
@end
- 交叉方法,寫在
June.m里
- 交叉方法,寫在
-
dispatch_once這里表示,保證方法替換只執(zhí)行一次,因?yàn)?+load可當(dāng)做普通類方法調(diào)用,所以,為了避免不小心手動(dòng)調(diào)用了+load而造成我們的方法實(shí)現(xiàn)替換效果失效,一般交叉方法替換系統(tǒng)方法或第三方不可見源代碼的場(chǎng)景,均替換一次方法實(shí)現(xiàn); - 另一種看我們的實(shí)際需要,如果希望在某些時(shí)刻兩種方法實(shí)現(xiàn)動(dòng)態(tài)交替?zhèn)€性效果,也可不寫
dispatch_once,通過手動(dòng)調(diào)用+load實(shí)現(xiàn)不同個(gè)性效果
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL oriSEL = @selector(Vampire);
SEL swiSEL = @selector(JuneSwizzle);
Method oriMet = class_getInstanceMethod(self, oriSEL);
Method swiMet = class_getInstanceMethod(self, swiSEL);
BOOL didAddMet = class_addMethod(self,
oriSEL,
method_getImplementation(swiMet),
method_getTypeEncoding(swiMet));
if (didAddMethod) {
class_replaceMethod(self,
swiSEL,
method_getImplementation(oriMet),
method_getTypeEncoding(oriMet));
NSLog(@" Add ");
} else {
method_exchangeImplementations(oriMet, swiMet);
NSLog(@" _exchange ");
}
});
}
-
周全起見,有
2種情況要考慮- 1>
-Vampire沒有在目標(biāo)類June中實(shí)現(xiàn),而是在父類Vampire中實(shí)現(xiàn)了。 - 2>
-Vampire已經(jīng)在目標(biāo)類June中重寫實(shí)現(xiàn) - 這兩種情況要區(qū)別對(duì)待。
- 1>
運(yùn)行時(shí)函數(shù)
class_addMethod用來檢查目標(biāo)類June中 是否有-Vampire的重寫實(shí)現(xiàn)-
if (didAddMethod)添加成功:說明
目標(biāo)類June中-Vampire沒有 重寫實(shí)現(xiàn),屬情況1>
□ 那么在目標(biāo)類June增加的 方法和實(shí)現(xiàn),方法名肯定是Vampire,但是方法實(shí)現(xiàn)是交換方法-JuneSwizzle的實(shí)現(xiàn)和其TypeEncoding
□ 然后用class_replaceMethod將-JuneSwizzle的實(shí)現(xiàn) 替換為-Vampire的實(shí)現(xiàn)和其TypeEncoding
□ 這里看起來可能會(huì)有點(diǎn)繞,雖然有點(diǎn)繞,但認(rèn)真思考1分鐘會(huì)發(fā)現(xiàn),交叉方法的實(shí)質(zhì)是,2個(gè)方法名還是原來的方法名,只是他們2個(gè)的方法實(shí)現(xiàn)交換了,所以這里的class_addMethod、class_replaceMethod就是為了交換2個(gè)方法的實(shí)現(xiàn),所以這樣寫,一點(diǎn)也不饒 :)添加失敗:說明
目標(biāo)類June中-Vampire已經(jīng) 重寫實(shí)現(xiàn)
□ 那么就是情況2>,可以直接通過method_exchangeImplementations來完成 交叉方法
為了更清晰的看到,
class_addMethod、class_replaceMethod這兩個(gè)運(yùn)行時(shí)函數(shù)的效果,我們來看一下下面的截圖,關(guān)于情況1>情況2>就不需要截圖演示了,因?yàn)楦居貌坏侥莾蓚€(gè)運(yùn)行時(shí)函數(shù)我們實(shí)現(xiàn)
情況1>的情形,但不實(shí)現(xiàn)那兩個(gè)運(yùn)行時(shí)函數(shù),看是怎樣的打印結(jié)果(實(shí)現(xiàn)那兩個(gè)運(yùn)行時(shí)函數(shù)的截圖也不需要演示了,肯定是交換方法成功了,肯定是想要的打印結(jié)果 :) )

對(duì)于情況1>,因?yàn)?class_getInstanceMethod 會(huì)返回父類Vampire的實(shí)現(xiàn),如果直接替換,就會(huì)替換掉父類Vampire的實(shí)現(xiàn),而不是目標(biāo)類June中的實(shí)現(xiàn)。(詳細(xì)的函數(shù)說明在這里)
舉個(gè)具體的例子, 假設(shè)要替換掉-[NSArray description],如果 NSArray 沒有實(shí)現(xiàn)-description (可選的) 那你就會(huì)得到NSObject的方法。如果調(diào)用method_exchangeImplementations, 你就會(huì)把 NSObject 的方法替換成你的代碼。這應(yīng)該不會(huì)是你想要的吧?