MethodSwizzling的實(shí)現(xiàn)
首先oc中的方法,是一個(gè) objc_method
結(jié)構(gòu)體,由三部分組成
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name // 方法sel
char * _Nullable method_types // 方法參數(shù)
IMP _Nonnull method_imp // 方法實(shí)現(xiàn)
}
IMP
指向了方法的實(shí)現(xiàn)
我們常使用objc/runtime.h
中提供的api:method_exchangeImplementations(Method m1, Method m2)
來(lái)交換IMP,重新綁定sel和 IMP對(duì)應(yīng)關(guān)系
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
method_exchangeImplementations(oriMethod, swiMethod);
但class_getInstanceMethod
函數(shù),在當(dāng)前類(lèi)沒(méi)有實(shí)現(xiàn)方法時(shí),會(huì)返回父類(lèi)的方法
method_exchangeImplementations
函數(shù)僅僅交換了兩個(gè) Method 結(jié)構(gòu)體的 imp,上面的代碼,當(dāng)子類(lèi)沒(méi)有實(shí)現(xiàn)oriMethod
時(shí)會(huì)將父類(lèi)和子類(lèi)方法的實(shí)現(xiàn)互換.
因此可以使用下面的方式
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
method_exchangeImplementations(oriMethod, swiMethod);
// 添加方法
if(class_addMethod(self, @selector(method1), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod))){
// 如果添加成功了,當(dāng)前類(lèi)沒(méi)有該方法,需要把swiMethod的實(shí)現(xiàn)替換成oriMethod的.
class_replaceMethod(self, @selector(method2), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}
else{
// 如果添加失敗了,當(dāng)前類(lèi)有這個(gè)方法
method_exchangeImplementations(oriMethod, swiMethod);
}
或者使用下面這種方式
// 確保當(dāng)前類(lèi),兩個(gè)方法都實(shí)現(xiàn)了,再去交換
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
class_addMethod(self,
@selector(method1),
class_getMethodImplementation(self, @selector(method1)),
method_getTypeEncoding(oriMethod));
class_addMethod(self,
@selector(method2),
class_getMethodImplementation(self, @selector(method2)),
method_getTypeEncoding(swiMethod));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(method1)), class_getInstanceMethod(self, @selector(method1)));
隱患
只在 +load 中執(zhí)行 swizzling 才是安全的败徊。
被 hook 的方法必須是當(dāng)前類(lèi)自身的方法趾徽,如果把繼承來(lái)的 IMP copy 到自身上面會(huì)存在問(wèn)題。父類(lèi)的方法應(yīng)該在調(diào)用的時(shí)候使用奶段,而不是 swizzling 的時(shí)候 copy 到子類(lèi)库糠。
被 Swizzled 的方法如果依賴(lài) cmd 伙狐,hook 之后 cmd 發(fā)送了變化,就會(huì)有問(wèn)題(一般你 hook 的是系統(tǒng)類(lèi),也不知道系統(tǒng)用沒(méi)用 cmd 這個(gè)參數(shù))鳞骤。
命名如果沖突導(dǎo)致之前 hook 的失效 或者是循環(huán)調(diào)用窒百。
第一條和第四條說(shuō)的是通常 MethodSwizzling 是在分類(lèi)里面實(shí)現(xiàn)的, 而分類(lèi)的 Method 是被Runtime 加載的時(shí)候追加到類(lèi)的 MethodList ,如果不在 +load
中執(zhí)行, Swizzling 一旦出現(xiàn)重名豫尽,那么 SEL 和 IMP 不匹配致 hook 的結(jié)果是循環(huán)調(diào)用篙梢。
第三條是一個(gè)不容易被發(fā)現(xiàn)的問(wèn)題。
我們都知道 Objective-C Method 都會(huì)有兩個(gè)隱含的參數(shù) self
,cmd
美旧,有的時(shí)候開(kāi)發(fā)者在使用關(guān)聯(lián)屬性的適合可能懶得聲明 (void *) 的 key渤滞,直接使用 cmd 變量 objc_setAssociatedObject(self, _cmd, xx, 0); 這會(huì)導(dǎo)致對(duì)當(dāng)前IMP對(duì) cmd 的依賴(lài)。
一旦此方法被 Swizzling榴嗅,那么方法的 cmd 勢(shì)必會(huì)發(fā)生變化妄呕,出現(xiàn)了 bug 之后想必一定找不到,
Copy父類(lèi)的方法帶來(lái)的隱患
上面的兩種 Swizzling 方式,存在copy父類(lèi)方法帶來(lái)的隱患
當(dāng)父類(lèi)中存在一個(gè) 方法.
子類(lèi)的分類(lèi) hook 父類(lèi)方法時(shí),會(huì)把父類(lèi)實(shí)現(xiàn) copy到子類(lèi).
這時(shí)父類(lèi)的分類(lèi)再去 hook 父類(lèi)的 方法,會(huì)導(dǎo)致子類(lèi) Hook 不到父類(lèi)的分類(lèi)的實(shí)現(xiàn)
這時(shí)可以使用RSSwizzle
RSSwizzleInstanceMethod([Student class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// 調(diào)用之前的實(shí)現(xiàn)
RSSWCallOriginal();
// 自身的實(shí)現(xiàn)
NSLog(@"Student + swizzle say hello sencod time");
}), 0, NULL);
RSSwizzleInstanceMethod([Person class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// 調(diào)用之前的實(shí)現(xiàn)
RSSWCallOriginal();
// 自身的實(shí)現(xiàn)
NSLog(@"Person + swizzle say hello");
}), 0, NULL);
將宏展開(kāi)
// 創(chuàng)建block
RSSwizzleImpFactoryBlock newImp = ^id(RSSwizzleInfo *swizzleInfo) {
void (*originalImplementation_)(__unsafe_unretained id,SEL));
SEL selector_ = @selector(sayHello);
return ^void (__unsafe_unretained id self) {
// 調(diào)用父類(lèi)的實(shí)現(xiàn)
((__typeof(originalImplementation_))[swizzleInfo getOriginalImplementation])(self, selector_);
//只有這一行是我們的核心邏輯
NSLog(@"Student + swizzle say hello");
};
};
[RSSwizzle swizzleInstanceMethod:@selector(sayHello)
inClass:[[Student class] class]
newImpFactory:newImp
mode:0 key:((void*)0)];;
其中RSSwizzleInfo
的聲明和 swizzleInstanceMethod:inClass:newImpFactory:newImp:mode:key:
方法的實(shí)現(xiàn)
/*
其中
typedef id (^RSSwizzleImpFactoryBlock)(RSSwizzleInfo *swizzleInfo);
*/
@interface RSSwizzleInfo : NSObject
/**
返回swizzled方法的原始實(shí)現(xiàn)嗽测。
如果swizzled類(lèi)實(shí)現(xiàn)了方法本身绪励,那么它實(shí)際上是一個(gè)原始實(shí)現(xiàn);如果沒(méi)有實(shí)現(xiàn)方法,則是從父類(lèi)獲取的實(shí)現(xiàn)唠粥。
@注意:調(diào)用時(shí)疏魏,必須始終將返回的實(shí)現(xiàn)強(qiáng)制轉(zhuǎn)換為相應(yīng)的函數(shù)指針。
@返回指向swizzled方法的原始實(shí)現(xiàn)的函數(shù)指針晤愧。
*/
-(RSSwizzleOriginalIMP)getOriginalImplementation;
/// The selector of the swizzled method.
@property (nonatomic, readonly) SEL selector;
@end
*/
typedef NS_ENUM(NSUInteger, RSSwizzleMode) {
///RSSwizzle總是做swizzing大莫。
RSSwizzleModeAlways = 0,
/// 如果以前用同一個(gè)鍵 RSSwizzle 過(guò)同一個(gè)類(lèi),就不會(huì)RSSwizzle官份。
RSSwizzleModeOncePerClass = 1,
/// 如果同一個(gè)類(lèi)或父類(lèi)之前已經(jīng)用同一個(gè)鍵進(jìn)行了swizzle只厘,就不會(huì)RSSwizzle。
RSSwizzleModeOncePerClassAndSuperclasses = 2
};
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key
{
if (key){
// 獲得key對(duì)應(yīng)的set
NSSet *swizzledClasses = swizzledClassesForKey(key);
if (mode == RSSwizzleModeOncePerClass) {
// 是否用同一個(gè)key RSSwizzle 過(guò)同一個(gè)類(lèi)
if ([swizzledClasses containsObject:classToSwizzle]){
return NO;
}
}else if (mode == RSSwizzleModeOncePerClassAndSuperclasses){
for (Class currentClass = classToSwizzle;
nil != currentClass;
currentClass = class_getSuperclass(currentClass))
{
if ([swizzledClasses containsObject:currentClass]) {
return NO;
}
}
}
}
swizzle(classToSwizzle, selector, factoryBlock);
if (key){
// 保存key
[swizzledClassesForKey(key) addObject:classToSwizzle];
}
}
return YES;
}
RSSwizzle核心代碼其實(shí)只有一個(gè)函數(shù),
// 刪除無(wú)關(guān)的加鎖舅巷,防御邏輯羔味,簡(jiǎn)化理解。
static void swizzle(Class classToSwizzle,
SEL selector,
RSSwizzleImpFactoryBlock factoryBlock)
{
Method method = class_getInstanceMethod(classToSwizzle, selector);
__block IMP originalIMP = NULL;
RSSWizzleImpProvider originalImpProvider = ^IMP{
// 保存 class_replaceMethod 獲得之前的實(shí)現(xiàn),如果之前沒(méi)有實(shí)現(xiàn),
IMP imp = originalIMP;
// 如果imp不存在,則獲取父類(lèi)的方法的imp.
if (NULL == imp){
imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
}
return imp;
};
RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
swizzleInfo.selector = selector;
swizzleInfo.impProviderBlock = originalImpProvider;
// 執(zhí)行傳進(jìn)來(lái)的block, 獲得 替換后的實(shí)現(xiàn)
id newIMPBlock = factoryBlock(swizzleInfo);
// 獲得返回值類(lèi)型
const char *methodType = method_getTypeEncoding(method);
// 創(chuàng)建實(shí)現(xiàn)
IMP newIMP = imp_implementationWithBlock(newIMPBlock);
// 替換方法,并獲得之前的實(shí)現(xiàn)
originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
}
通過(guò)閱讀源碼可知, RSSwizzle 并沒(méi)有去交換方法的imp,而是,保存舊的imp, 然后新的方法替換了舊的方法.