RSSwizzle
是一個簡單的hook
函數(shù)的第三方庫,它的使用跟傳統(tǒng)的hook
方式比起來更加便捷澄惊,也更加安全。下面來分析它是怎么做到的富雅。
傳統(tǒng)的hook方法
實現(xiàn)
一般的掸驱,如果我們要viewDidLoad
,我們需要寫如下的代碼:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSel = @selector(viewDidLoad);
SEL swizzleSel = @selector(swizzle_viewDidLoad);
Method originalMethod =
class_getInstanceMethod([self class], originalSel);
Method swizzleMethod = class_getInstanceMethod([self class], swizzleSel);
1.
BOOL didAddMethod = class_addMethod(
[self class], originalSel, method_getImplementation(swizzleMethod),
method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
2.
class_replaceMethod([self class], swizzleSel,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
3.
method_exchangeImplementations(originalMethod, swizzleMethod);
}
});
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)swizzle_viewDidLoad {
[self swizzle_viewDidLoad];
NSLog(@"hook viewDidLoad");
}
如上所示,主要的邏輯都在load函數(shù)里面没佑,其核心思路就是交換viewDidLoad
和`swizzle_viewDidLoad``的實現(xiàn)毕贼,下面簡單解析一下:
- 嘗試給當前類添加一個方法,該方法的方法名是
viewDidLoad
蛤奢,實現(xiàn)是swizzle_viewDidLoad
的實現(xiàn)鬼癣,這么做的目的是為了確保當前類一定有viewDidLoad
這個方法名(否則使用method_exchangeImplementations交換不會成功) - 添加成功,則將原來的
swizzle_viewDidLoad
的實現(xiàn)替換換成viewDidLoad的實現(xiàn) - 如果添加不成功啤贩,則交換兩個方法的實現(xiàn)
這樣之后待秃,只要viewDidLoad
被調(diào)用,則會走到swizzle_viewDidLoad
的實現(xiàn)上來痹屹,而swizzle_viewDidLoad
調(diào)用自己則走回原來的viewDidLoad
的實現(xiàn)锥余,從而實現(xiàn)了hook
不足之處
這樣寫,大部分情況都是可以實現(xiàn)hook的痢掠,但是還是有一些邊界情況沒有考慮進去驱犹,比如originalSel在本類和父類都沒有實現(xiàn)的情況,可以參考這篇文章足画。另外雄驹,沒有一個hook就會要多寫一個方法,寫法上也不是很好淹辞。還有就是医舆,hook的代碼一旦不是寫在load
函數(shù)里面(一般不會出現(xiàn)這種情況),則還要考慮多線程的問題。
RSSwizzle
RSSwizzle能規(guī)避上述的問題蔬将,如果要hook一個函數(shù)爷速,不管在什么地方,只需要這么寫:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RSSwizzleInstanceMethod([self class], @selector(viewDidLoad), RSSWReturnType(void), RSSWArguments(), RSSWReplacement({
RSSWCallOriginal();
NSLog(@"hook viewDidLoad");
}), RSSwizzleModeAlways, NULL)
});
其中RSSwizzleInstanceMethod
就是交換方法的宏霞怀,除了要傳viewDidLoad之外惫东,還要傳入方法的返回參數(shù)和方法的參數(shù),在block里面毙石,就是替換的實現(xiàn)廉沮,其中RSSWCallOriginal
是另一個宏,就是調(diào)用原來的方法徐矩。
可以看出滞时,這樣調(diào)用比原來的方式要簡潔多了。
RSSwizzle代碼實現(xiàn)
在RSSwizzle.h文件中滤灯,定義了兩個類坪稽,RSSwizzleInfo
用于保存原函數(shù)的實現(xiàn),RSSwizzle
則是swizzle的主要類鳞骤,其中有兩個方法
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key;
+(void)swizzleClassMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock;
看函數(shù)名可以知道窒百,這個兩個方法一個是針對類方法的swizzle一個是針對實例方法的swizzle。先看一下針對類方法的實現(xiàn):
+(void)swizzleClassMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
{
[self swizzleInstanceMethod:selector
inClass:object_getClass(classToSwizzle)
newImpFactory:factoryBlock
mode:RSSwizzleModeAlways
key:NULL];
}
可以看出弟孟,其實最后還是調(diào)用swizzleInstanceMethod
贝咙,只是把該類對象的元類傳進去而已。swizzleInstanceMethod
的代碼如下:
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key
{
NSAssert(!(NULL == key && RSSwizzleModeAlways != mode),
@"Key may not be NULL if mode is not RSSwizzleModeAlways.");
@synchronized(swizzledClassesDictionary()){
if (key){
1.
NSSet *swizzledClasses = swizzledClassesForKey(key);
if (mode == RSSwizzleModeOncePerClass) {
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;
}
}
}
}
2.
swizzle(classToSwizzle, selector, factoryBlock);
if (key){
[swizzledClassesForKey(key) addObject:classToSwizzle];
}
}
return YES;
}
-
RSSwizzleMode
是一個枚舉拂募,用于決定這次hook是否能hook多次還是只能hook一次(或者是父類hook一次)庭猩,它會根據(jù)key
對應的集合是否有當前要hook的類決定是否使用這次hook,通常在開發(fā)中mode會傳RSSwizzleModeAlways
陈症,key
會傳NULL蔼水,因此代碼會直接走到2這邊來。PS感覺這個功能
比較雞肋录肯,如果別的模塊使用傳統(tǒng)的swizzle方法還是會hook住的趴腋,實在想不到應用場景。 - 這是swizzle的核心方法论咏,
RSSwizzleImpFactoryBlock
的定義如下:
typedef id (^RSSwizzleImpFactoryBlock)(RSSwizzleInfo *swizzleInfo);
swizzle
方法的實現(xiàn)如下:
static void swizzle(Class classToSwizzle,
SEL selector,
RSSwizzleImpFactoryBlock factoryBlock)
{
1.
Method method = class_getInstanceMethod(classToSwizzle, selector);
NSCAssert(NULL != method,
@"Selector %@ not found in %@ methods of class %@.",
NSStringFromSelector(selector),
class_isMetaClass(classToSwizzle) ? @"class" : @"instance",
classToSwizzle);
2.
NSCAssert(blockIsAnImpFactoryBlock(factoryBlock),
@"Wrong type of implementation factory block.");
3.
__block OSSpinLock lock = OS_SPINLOCK_INIT;
__block IMP originalIMP = NULL;
4.
RSSWizzleImpProvider originalImpProvider = ^IMP{
OSSpinLockLock(&lock);
IMP imp = originalIMP;
OSSpinLockUnlock(&lock);
if (NULL == imp){
Class superclass = class_getSuperclass(classToSwizzle);
imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
}
return imp;
};
5.
RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
swizzleInfo.selector = selector;
swizzleInfo.impProviderBlock = originalImpProvider;
6.
id newIMPBlock = factoryBlock(swizzleInfo);
const char *methodType = method_getTypeEncoding(method);
NSCAssert(blockIsCompatibleWithMethodType(newIMPBlock,methodType),
@"Block returned from factory is not compatible with method type.");
IMP newIMP = imp_implementationWithBlock(newIMPBlock);
7.
OSSpinLockLock(&lock);
originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
OSSpinLockUnlock(&lock);
}
- 獲取原方法的method
- 確保factoryBlock是否是RSSwizzleImpFactoryBlock优炬,如果傳的是
id(NSObject *obj){}
類型的block編譯器不會報錯,但在這里會進斷言厅贪,其實就是判斷二者的簽名是否一致蠢护,這里就不展開講了,有興趣可以看這篇文章 -
originalIMP
是原來方法的實現(xiàn)养涮,目前是NULL,后續(xù)會給它賦值葵硕,它可能是線程不安全的(因為引用它的block不知道會被哪個線程調(diào)用)眉抬,因此需要加鎖保護 -
originalImpProvider
就是返回一個原來方法的實現(xiàn),如果本類沒有懈凹,還會往父類那里找直到找到為止蜀变。這是因為7中的class_replaceMethod
只會返回本類的實現(xiàn),不會再父類中查找 - 初始化
RSSwizzleInfo
介评,給它賦值 - 調(diào)用
factoryBlock
库北,拿到新的block,然后比較這個block跟原函數(shù)的函數(shù)簽名是否一致,否則進斷言威沫,最后用這個block初始化newIMP
-
class_replaceMethod
替換原來的實現(xiàn)贤惯,如果本類沒有這個方法洼专,會默認加上這個替換的方法棒掠,返回的originalIMP
就是原來的實現(xiàn)
從6可以看出,傳入的factoryBlock
的返回只能是一個block屁商,否則這邏輯走不通烟很,這個函數(shù)執(zhí)行完之后,調(diào)用原來的函數(shù)蜡镶,就執(zhí)行了newIMP
此外雾袱,如果想在替換的方法里面調(diào)用原來的函數(shù),我們就需要在RSSwizzleInfo
那里拿到原來的實現(xiàn)了官还,主要函數(shù)如下:
-(RSSwizzleOriginalIMP)getOriginalImplementation{
NSAssert(_impProviderBlock,nil);
return (RSSwizzleOriginalIMP)_impProviderBlock();
}
不難看出芹橡,其實就是調(diào)用4中的block取得IMP而已
如果只是通過swizzleInstanceMethod
這個函數(shù)來實現(xiàn)swizzle,那么我們必須要傳對RSSwizzleImpFactoryBlock
望伦,否則會進入斷言林说,這樣調(diào)用還是挺麻煩的,但是它提供的宏解決了這一問題屯伞。
RSSwizzle宏實現(xiàn)
使用RSSwizzle
進行hook的時候會使用到它的很多個宏腿箩,下面從它的參數(shù)開始說起:
RSSWReturnType
#define RSSWReturnType(type) type
這是一個返回值的宏,可以填寫可以看出這個宏其實什么都沒做劣摇,原樣返回了珠移,但是這樣寫增加了代碼的易讀性
RSSWArguments
#define RSSWArguments(arguments...) _RSSWArguments(arguments)
#define _RSSWArguments(arguments...) DEL, ##arguments
這是一個可以填多個參數(shù)的宏,...是多個參數(shù)的意思末融,arguments
是這些參數(shù)的集合钧惧。
##
在這里的意思是:如果沒有arguments,則刪掉##arguments和前面的逗號勾习,也就是說浓瞪,RSSWArguments()
最后得到的是DEL
,而所有的傳參前面都會插入DEL
這個標志位,后續(xù)會移除這個標志位语卤,這樣做是為了規(guī)避沒有參數(shù)的時候有時預編譯會出現(xiàn)多余逗號的bug追逮。
RSSWReplacement
#define RSSWReplacement(code...) code
這個宏封裝替換的函數(shù)
RSSwizzleInstanceMethod
#define RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
RSSWArguments, \
RSSWReplacement, \
RSSwizzleMode, \
key) \
_RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
_RSSWWrapArg(RSSWArguments), \
_RSSWWrapArg(RSSWReplacement), \
RSSwizzleMode, \
key)
#define _RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
RSSWArguments, \
RSSWReplacement, \
RSSwizzleMode, \
KEY) \
[RSSwizzle \
swizzleInstanceMethod:selector \
inClass:[classToSwizzle class] \
newImpFactory:^id(RSSwizzleInfo *swizzleInfo) { \
RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id, \
SEL, \
RSSWArguments)); \
SEL selector_ = selector; \
return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self, \
RSSWArguments)) \
{ \
RSSWReplacement \
}; \
} \
mode:RSSwizzleMode \
key:KEY];
這個是最主要的宏酪刀,它封裝了整個函數(shù)的調(diào)用,將其展開那就是:
[RSSwizzle
swizzleInstanceMethod:selector
inClass:[classToSwizzle class]
newImpFactory:^id(RSSwizzleInfo *swizzleInfo) {
1.
RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id,
SEL,
RSSWArguments));
2.
SEL selector_ = selector;
3.
return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self,
RSSWArguments))
{
RSSWReplacement
};
}
mode:RSSwizzleMode
key:KEY];
前兩個參數(shù)都挺好懂钮孵,主要看最后一個參數(shù):
1.根據(jù)函數(shù)參數(shù)和返回值聲明一個名為originalImplementation_
的block骂倘,改block的返回值是RSSWReturnType
,也就是傳入的返回值巴席,參數(shù)值是id,SEL,和RSSWArguments
中傳入的參數(shù)历涝,_RSSWDel3Arg
的宏定義如下:
#define _RSSWDel3Arg(a1, a2, a3, args...) a1, a2, ##args
這是為了去掉第三個參數(shù),前面說過RSSWArguments
會在參數(shù)前面插入一個DEL漾唉,就在這里去掉了
2.定義selector_
等于selector
荧库,這是原函數(shù)的方法編號
3.定義了一個block作返回,這個block返回值是RSSWReturnType
赵刑,參數(shù)是self和RSSWArguments
中傳入的參數(shù)分衫,這里_RSSWDel2Arg
的意思跟_RSSWDel3Arg
類似,都是除掉多余的DEL,這個block的內(nèi)容就是替換的函數(shù)般此,從上面的代碼分析中我們知道蚪战,這個block就是用來初始化newIMP
的。
RSSWCallOriginal
在hook的時候很多時候都需要調(diào)用會之前的函數(shù)铐懊,這個時候就要調(diào)用RSSWCallOriginal
這個宏了邀桑,其定義如下:
#define RSSWCallOriginal(arguments...) _RSSWCallOriginal(arguments)
#define _RSSWCallOriginal(arguments...) \
((__typeof(originalImplementation_))[swizzleInfo \
getOriginalImplementation])(self, \
selector_, \
##arguments)
可以看出,它是在swizzleInfo
中拿到imp指針科乎,然后將其強制轉(zhuǎn)換為originalImplementation_
這個block進行調(diào)用壁畸,這樣的好處由于originalImplementation_
的傳參和返回值都由外界決定,因此如果傳的不對編譯器會報錯茅茂,在一定程度上避免在運行期間進入斷言捏萍。
有了這些宏之后,hook就變得方便多了
總結(jié)
RSSwizzle
雖然是個輕量易用的庫玉吁,總共的代碼量不多照弥,但涉及到的知識還是挺多的,其中運行時和宏的相關(guān)的代碼更是非常的精妙进副,推薦大家使用这揣。