class_replaceMethod與method_exchangeImplementations區(qū)別
方法交換在開發(fā)中還是挺常見的,比如hook調(diào)viewDidLoad方法,想在每個(gè)viewDidLoad里面打印出當(dāng)前類名,可以寫個(gè)jm_ viewDidLoad方法,在用runtime交換倆方法的實(shí)現(xiàn)(也叫IMP)。

看不少開源庫(kù)都用到方法交換,基本有倆種實(shí)現(xiàn)方式:
第一種實(shí)現(xiàn):
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){
Method originalMethod = class_getInstanceMethod(cls, origSelector);
Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
IMP previousIMP = class_replaceMethod(cls, origSelector, method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
class_replaceMethod(cls, newSelector, previousIMP,method_getTypeEncoding(originalMethod));
}
第二種實(shí)現(xiàn):
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){
Method originalMethod = class_getInstanceMethod(cls, origSelector);
Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
在某些情況下,這倆種確實(shí)都能達(dá)到交換IMP的效果,但是其中又有一絲區(qū)別。
看看class_replaceMethod的文檔解釋:
方法:IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)
- Replaces the implementation of a method for a given class.
- @param cls The class you want to modify.
- @param name A selector that identifies the method whose implementation you want to replace.
- @param imp The new implementation for the method identified by name for the class identified by cls.
- @param types An array of characters that describe the types of the arguments to the method.
- Since the function must take at least two arguments-self and _cmd, the second and third characters
- must be “@:” (the first character is the return type).
- @return The previous implementation of the method identified by name for the class identified by \e cls.
- @note This function behaves in two different ways:
- If the method identified by name does not yet exist, it is added as if class_addMethod were called.
- If the method identified by name does exist, its IMP is replaced as if method_setImplementation were called.
重點(diǎn)是最后note的描述:當(dāng)name描述的方法不存在的時(shí)候,將調(diào)用class_addMethod方法;當(dāng)這個(gè)方法存在,將調(diào)用method_setImplementation方法。
再看看method_exchangeImplementations的文檔解釋:
方法:void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
- @param m1 Method to exchange with second method.
- @param m2 Method to exchange with first method.
- @note This is an atomic version of the following:
- IMP imp1 = method_getImplementation(m1);
- IMP imp2 = method_getImplementation(m2);
- method_setImplementation(m1, imp2);
- method_setImplementation(m2, imp1);
可以看出這個(gè)方法完全是個(gè)二愣子,直接交換IMP,啥都不管。
倆中方法都用到了class_getInstanceMethod(cls, selector)方法,這個(gè)方法有個(gè)特點(diǎn):如果這個(gè)類中沒有實(shí)現(xiàn)selector這個(gè)方法,它返回的是它某父類的 Method 對(duì)象(沿著繼承鏈找到為止)。
重點(diǎn)來了:
如果這個(gè)類沒實(shí)現(xiàn)這個(gè)方法,但是它父類實(shí)現(xiàn)了,直接拿這方法來交換,真沒問題么??
先說結(jié)果:第二種實(shí)現(xiàn)有問題,第一種實(shí)現(xiàn)沒問題。
看代碼上代碼:
@interface NSObject(test)
@end
@implementation NSObject(test)
- (void)oneSwizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector
{ Class cls = [self class];
Method originalMethod = class_getInstanceMethod(cls, origSelector);
Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
IMP previousIMP = class_replaceMethod(cls,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
class_replaceMethod(cls,
newSelector,
previousIMP,
method_getTypeEncoding(originalMethod));
}
- (void)twoSwizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
Class cls = [self class];
Method originalMethod = class_getInstanceMethod(cls, origSelector);
Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end
@interface Base : NSObject
@end
@implementation Base
- (void)print:(NSString*)msg
{
NSLog(@"print-->obj %@ print say:%@", NSStringFromClass(self.class), msg);
}
- (void)hookPrint:(NSString*)msg {
NSLog(@"hookPrin-->obj %@ print say:%@", NSStringFromClass(self.class), msg);
}
@end
//A只實(shí)現(xiàn)了print方法,沒有實(shí)現(xiàn)hookPrint方法。
@interface A : Base
@end
@implementation A
- (void)print:(NSString*)msg {
NSLog(@"A obj print say:%@", msg);
}
@end
//B啥都沒實(shí)現(xiàn)。
@interface B : Base
@end
@implementation B
@end
現(xiàn)在我們測(cè)試第一種實(shí)現(xiàn):分別交換A、B的print和hookPrint的方法實(shí)現(xiàn)。
[A oneSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)];
[B oneSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)];
//測(cè)試代碼是這樣的:
A* a = [A new]; [a print:@"hello1"];
B* b = [B new]; [b print:@"hello2"];
//結(jié)果:
2019-07-02 14:55:19.294580+0800 xctest[3533:667117] hookPrin-->obj A print say:hello1
2019-07-02 14:55:19.294871+0800 xctest[3533:667117] hookPrin-->obj B print say:hello2
說明A、B確實(shí)交換了方法實(shí)現(xiàn)。
現(xiàn)在我們測(cè)試第二種實(shí)現(xiàn):分別交換A、B的print和hookPrint的方法實(shí)現(xiàn)。
[A twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)];
[B twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)];
//測(cè)試代碼是這樣的:
A* a = [A new]; [a print:@"hello1"];
B* b = [B new]; [b print:@"hello2"];
//結(jié)果:
2019-07-02 14:58:48.692403+0800 xctest[3585:681651] hookPrin-->obj A print say:hello1
2019-07-02 14:58:48.692697+0800 xctest[3585:681651] A obj print say:hello2
嗯?有沒不對(duì)勁?B的交換方法失敗了。不是期望中hookPrin-->obj B print say:hello2。
why?分析下:
第一種方法,用class_replaceMethod()實(shí)現(xiàn)的。由于A中不存在hookPrint方法,class_replaceMethod會(huì)調(diào)用class_addMethod方法,而class_addMethod會(huì)把Base的hookPrint實(shí)現(xiàn)添加到當(dāng)前類,print的實(shí)現(xiàn)最終會(huì)和hookPrint的實(shí)現(xiàn)交換。B中倆方法都不存在,也會(huì)添加倆方法,最終交換倆方法的實(shí)現(xiàn)。最終打印的時(shí)候,會(huì)調(diào)用Base的hookPrint方法。
如果調(diào)用[a hookPrint:@"hello_k"];那么最終實(shí)現(xiàn)的應(yīng)該是A類中print的實(shí)現(xiàn)。結(jié)果是:A obj print say:hello_k。
第二種方法,用method_exchangeImplementations實(shí)現(xiàn)。由于A沒有實(shí)現(xiàn)hookPrint方法,在調(diào)用
[A twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的時(shí)候,將A的print實(shí)現(xiàn)與Base的hookPrint實(shí)現(xiàn)交換了。
接著調(diào)用 [B twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的時(shí)候,由于B類啥都沒實(shí)現(xiàn),它只能將Base的print實(shí)現(xiàn)與base的hookPrint交換了。最終,調(diào)用[b print:@"hello2"]的時(shí)候,調(diào)用的是代碼中A類的print方法的實(shí)現(xiàn)。
這里加不加class_addMethod的判斷,結(jié)果都一樣??梢宰约涸囋?。
測(cè)試代碼地址:github鏈接
注意:整篇這是沒考慮交換方法不存在的情況下考量的。
結(jié)論:當(dāng)我們寫的類沒有繼承的關(guān)系的時(shí)候,倆種方法都沒什么問題。當(dāng)有繼承關(guān)系又不確定方法實(shí)現(xiàn)沒實(shí)現(xiàn),最好用class_replaceMethod方法。當(dāng)啥都不確定的時(shí)候,老老實(shí)實(shí)地用class_replaceMethod 吧,安全無(wú)痛苦。