前方極其燒腦漏策,建議->點(diǎn)贊再看
本文承接上一篇文章iOS-玩轉(zhuǎn)Block(從入門到底層原理)西设,如果還沒(méi)看的話建議先閱讀一下哑诊,會(huì)對(duì)block
的底層原理有更深一層的理解喳张,然后再閱讀此文必會(huì)事半功倍哥遮。
這一篇文章的誕生源于我在網(wǎng)上看到一位大神
發(fā)的面試題岂丘,如下
以下題目都針對(duì)于任意
void (^)(void)
形式的block
:
- 1.實(shí)現(xiàn)下面的函數(shù),將
block
的實(shí)現(xiàn)修改成NSLog(@"Hello world")
眠饮,也就說(shuō)奥帘,在調(diào)用完這個(gè)函數(shù)后調(diào)用block()
時(shí),并不調(diào)用原始實(shí)現(xiàn)仪召,而是打"Hello world
"
void HookBlockToPrintHelloWorld(id block){
}
- 2.實(shí)現(xiàn)下面的函數(shù)寨蹋,將
block
的實(shí)現(xiàn)修改成打印所有入?yún)ⅲ⒄{(diào)用原始實(shí)現(xiàn)
//
//比如
// void(^block)(int a, NSString *b) = ^(int a, NSString *b){
// NSLog(@"block invoke");
// }
// HookBlockToPrintArguments(block);
// block(123,@"aaa");
// //這里輸出"123, aaa"和"block invoke"
//
void HookBlockToPrintArguments(id block){
}
- 3.實(shí)現(xiàn)下面的函數(shù)扔茅,使得調(diào)用這個(gè)函數(shù)之后已旧,后面創(chuàng)建的任意
block
都能自動(dòng)實(shí)現(xiàn)第二題的功能
void HookEveryBlockToPrintArguments(void){
}
剛看到題目的時(shí)候以為跟方法交換(Method Change
)差不多的(不是很簡(jiǎn)單嗎?召娜?运褪?),然后玖瘸。秸讹。。
好吧店读,我錯(cuò)了嗦枢,事情遠(yuǎn)遠(yuǎn)沒(méi)有辣么簡(jiǎn)單
但是作為祖國(guó)的未來(lái)的希望,怎么能遇到問(wèn)題就退縮呢屯断。經(jīng)過(guò)一番的思考之后文虏,終于還是默默打開(kāi)了百度。殖演。氧秘。
這一搜,就進(jìn)入了踩坑之路趴久。丸相。。關(guān)于
hook block
的實(shí)現(xiàn)網(wǎng)上并沒(méi)有太多文章討論彼棍,但還是會(huì)有幾個(gè)大神默默分享灭忠,比如BlockHook,fishhook座硕,BlockHookDemo弛作,YSBlockHook...
功能都非常強(qiáng)大,于是我把這些框架的源碼都大概看了一遍华匾,這才醍醐灌頂
扯淡時(shí)間結(jié)束
接下來(lái)我會(huì)以解決以上三道題為目的映琳,分享實(shí)現(xiàn)的思路以及解決方案。
-
第一題 交換
block
的實(shí)現(xiàn)
首先我們來(lái)思考一下,如何交換一個(gè)Block
的實(shí)現(xiàn)萨西?
我們很容易能聯(lián)想到方法交換(關(guān)于方法交換推薦使用業(yè)內(nèi)比較出名的庫(kù)aspect有鹿,內(nèi)部源碼實(shí)現(xiàn)也是不同于一般人的做法,大神就是大神谎脯,建議閱讀幾遍4邪稀!T此蟆)
方法交換的原理就是在運(yùn)行期間動(dòng)態(tài)交換兩個(gè)方法所指向的IMP
指針年局,那么換作Block
也是一樣的道理,只不過(guò)難度要大得多咸产。
通過(guò)閱讀蘋果官方源碼libclosure矢否,前面我們分析Block
結(jié)構(gòu)體的時(shí)候有提到內(nèi)部存儲(chǔ)著invoke
指針,封裝著函數(shù)(方法)的地址脑溢,所以不難想到僵朗,只要將invoke
指針指向我們自定義的函數(shù)地址,就可以交換block
的實(shí)現(xiàn)了屑彻。
仿造蘋果源碼構(gòu)造了block
相關(guān)的結(jié)構(gòu)體验庙,方便我們操作
typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);
typedef void(*BlockInvokeFunction)(void *, ...);
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
typedef struct Block_layout *CJJBlockLayout;
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
定義一個(gè)類方法,將原始block
和目標(biāo)block
作為參數(shù)傳入社牲,將它們轉(zhuǎn)為CJJBlockLayout
類型的結(jié)構(gòu)體粪薛,分別取出它們的invoke
指針,然后相互交換invoke
指針的指向
//第一題
//- **1.實(shí)現(xiàn)下面的函數(shù)搏恤,將`block`的實(shí)現(xiàn)修改成`NSL(@"Hello world")`违寿,也就說(shuō),在調(diào)用完這個(gè)函數(shù)后調(diào)用`block()`時(shí)熟空,并不調(diào)用原始實(shí)現(xiàn)藤巢,而是打"`Hello world`"**
//```
void HookBlockToPrintHelloWorld(id block){
[CJJBlockHook hookOriginBlock:block hookBlock:^{
NSLog(@"第一題替換后:Hello world");
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
void (^originBlock1)(void) = ^{
NSLog(@"第一題替換前:你好");
};
originBlock1();
HookBlockToPrintHelloWorld(originBlock1);
originBlock1();
}
+ (void)hookOriginBlock:(id)originBlock hookBlock:(id)hookBlock{
NSParameterAssert(originBlock);
NSParameterAssert(hookBlock);
CJJBlockLayout originLayout = (__bridge CJJBlockLayout)originBlock;
CJJBlockLayout hookLayout = (__bridge CJJBlockLayout)hookBlock;
BlockInvokeFunction originInvoke = originLayout->invoke;
BlockInvokeFunction hookInvoke = hookLayout->invoke;
originLayout->invoke = hookInvoke;
hookLayout->invoke = originInvoke;
}
我們?cè)诮粨Q前和交換后都調(diào)用一次originBlock1()
,輸出結(jié)果如下
2020-09-01 22:02:13.690715+0800 CJJBlockHook[4421:71893] 第一題替換前:你好
2020-09-01 22:02:13.690900+0800 CJJBlockHook[4421:71893] 第一題替換后:Hello world
可以看到調(diào)用了函數(shù)HookBlockToPrintHelloWorld(originBlock1)
后再調(diào)用block
息罗,并沒(méi)有調(diào)用原始實(shí)現(xiàn)掂咒,而是打印"Hello world"
,第一題解決迈喉,非常簡(jiǎn)單绍刮。
-
第二題 將某個(gè)block的實(shí)現(xiàn)修改成打印所有入?yún)ⅲ⒄{(diào)用原始實(shí)現(xiàn)
第一題的那種做法雖然簡(jiǎn)單挨摸,但僅適用于交換兩個(gè)block
的實(shí)現(xiàn)孩革,并不能實(shí)現(xiàn)更多的功能,比如在調(diào)用原來(lái)的block
實(shí)現(xiàn)前或者實(shí)現(xiàn)后注入自己的代碼油坝,而這一題顯示是要我們?cè)诓挥绊懺瓉?lái)的調(diào)用的情況下在前面注入代碼嫉戚,打印傳入的參數(shù)。這時(shí)候我們可以利用runtime
消息轉(zhuǎn)發(fā)流程來(lái)實(shí)現(xiàn)澈圈。
+ (void)hookPrintParamsOriginBlock:(id)originBlock{
[self hookOriginBlock:originBlock hookBlock:^{} position:CJJBlockHookPositionDoNothing];
}
+ (void)hookOriginBlock:(id)originBlock hookBlock:(id)hookBlock position:(CJJBlockHookPosition)position{
NSParameterAssert(originBlock);
NSParameterAssert(hookBlock);
CJJBlockLayout originLayout = (__bridge CJJBlockLayout)originBlock;
CJJBlockLayout hookLayout = (__bridge CJJBlockLayout)hookBlock;
cjjHook_setPosition(originLayout, position);
cjjHook_setHookBlock(originLayout, hookLayout);
cjjHook_handleBlock(originBlock);
}
我們僅需要傳入原來(lái)的block
就可以了,同樣,先將block
轉(zhuǎn)化CJJBlockLayout
結(jié)構(gòu)體擒悬,然后保存position
typedef NS_ENUM(NSUInteger, CJJBlockHookPosition) {
CJJBlockHookPositionBefore = 0,
CJJBlockHookPositionAfter,
CJJBlockHookPositionReplace,
CJJBlockHookPositionDoNothing
};
使用的是關(guān)聯(lián)對(duì)象技術(shù)
static NSString * const CJJBlockHookKey_Position = @"CJJBlockHookKey_Position";
static void cjjHook_setPosition(CJJBlockLayout originLayout, CJJBlockHookPosition position){
objc_setAssociatedObject((__bridge id)originLayout, CJJBlockHookKey_Position.UTF8String, @(position), OBJC_ASSOCIATION_ASSIGN);
}
static NSNumber * cjjHook_getPosition(CJJBlockLayout originLayout){
NSNumber *position = objc_getAssociatedObject((__bridge id)originLayout, CJJBlockHookKey_Position.UTF8String);
if(!position){
position = @(CJJBlockHookPositionReplace);
}
return position;
}
可以在block
前后調(diào)用自己的實(shí)現(xiàn)或者直接替換掉原始block
的實(shí)現(xiàn)榕茧,這里我們可以選擇CJJBlockHookPositionDoNothing
,什么也不做诽偷,因?yàn)閮H僅是打印參數(shù)而已坤学,后面會(huì)講到。
保存自定義的block
(CJJBlockHookPositionBefore
报慕,CJJBlockHookPositionAfter
深浮,CJJBlockHookPositionReplace
這三種情況用到)
cjjHook_setHookBlock(originLayout, hookLayout);
同樣使用的也是關(guān)聯(lián)對(duì)象技術(shù)
static NSString * const CJJBlockHookKey_HookBlock = @"CJJBlockHookKey_HookBlock";
static void cjjHook_setHookBlock(CJJBlockLayout originLayout, CJJBlockLayout hookLayout){
objc_setAssociatedObject((__bridge id)originLayout, CJJBlockHookKey_HookBlock.UTF8String, (__bridge id)hookLayout, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static id cjjHook_getHookBlock(CJJBlockLayout blockLayout){
return objc_getAssociatedObject((__bridge id)blockLayout, CJJBlockHookKey_HookBlock.UTF8String);
}
重點(diǎn)從這里開(kāi)始
cjjHook_handleBlock(originBlock);
我們來(lái)看看它的實(shí)現(xiàn)
static void cjjHook_handleBlock(id originBlock){
//swizzling系統(tǒng)的消息轉(zhuǎn)發(fā)方法
cjjHook_hookMsgForwardMethod();
//拷貝origin block
CJJBlockLayout layout = (__bridge CJJBlockLayout)originBlock;
if(!cjjHook_getOriginBlock(layout)){
//深拷貝一份origin block的副本
cjjHook_deepCopy(layout);
//取出簽名
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
//指向消息轉(zhuǎn)發(fā)函數(shù)
layout->invoke = (void *)cjjHook_getMsgForward(desc3->signature);
}
}
首先交換了系統(tǒng)消息轉(zhuǎn)發(fā)流程的最后兩個(gè)方法methodSignatureForSelector:
和forwardInvocation:
,因?yàn)樵?code>forwardInvocation:里面我們可以做任何我們想做的事情眠冈。
static void cjjHook_hookMethod(SEL originSel, IMP hookIMP){
Class cls = NSClassFromString(@"NSBlock");
Method method = class_getInstanceMethod([NSObject class], originSel);
BOOL success = class_addMethod(cls, originSel, hookIMP, method_getTypeEncoding(method));
if(!success){
class_replaceMethod(cls, originSel, hookIMP, method_getTypeEncoding(method));
}
}
static void cjjHook_hookMsgForwardMethod(){
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cjjHook_hookMethod(@selector(methodSignatureForSelector:), (IMP)cjjHook_methodSignatureForSelector);
cjjHook_hookMethod(@selector(forwardInvocation:), (IMP)cjjHook_forwardInvocation);
});
}
接著把原來(lái)的block
拷貝一份保存起來(lái)飞苇,因?yàn)楹竺鏁?huì)將原來(lái)的block
的invoke
指針指向_objc_msgForward
或者_objc_msgForward_stret
,為了之后還能調(diào)用原來(lái)的實(shí)現(xiàn)蜗顽,所以這里需要拷貝一份布卡。拷貝的方法也是參考蘋果源碼里的_Block_copy
方法雇盖,有興趣的話可以直接閱讀源碼研究研究
static void cjjHook_deepCopy(CJJBlockLayout layout) {
struct Block_descriptor_2 *desc_2 = _Block_descriptor_2(layout);
//如果捕獲的變量存在對(duì)象或者被__block修飾的變量時(shí)忿等,在__main_block_desc_0函數(shù)內(nèi)部會(huì)增加copy跟dispose函數(shù),copy函數(shù)內(nèi)部會(huì)根據(jù)修飾類型(weak or strong)對(duì)對(duì)象進(jìn)行強(qiáng)引用還是弱引用崔挖,當(dāng)block釋放之后會(huì)進(jìn)行dispose函數(shù)贸街,release掉修飾對(duì)象的引用,如果都沒(méi)有引用對(duì)象狸相,將對(duì)象釋放
if (desc_2) {
CJJBlockLayout newLayout = malloc(layout->descriptor->size);
if (!newLayout) {
return;
}
memmove(newLayout, layout, layout->descriptor->size);
newLayout->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
newLayout->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
(desc_2->copy)(newLayout, layout);
cjjHook_setOriginBlock(layout, newLayout);
} else {
//FishBind缺陷:以前那種grouph方式?jīng)]辦法拷貝變量
CJJBlockLayout newLayout = malloc(layout->descriptor->size);
if (!newLayout) {
return;
}
memmove(newLayout, layout, layout->descriptor->size);
newLayout->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
newLayout->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
cjjHook_setOriginBlock(layout, newLayout);
}
}
if(!cjjHook_getOriginBlock(layout)){
//深拷貝一份origin block的副本
cjjHook_deepCopy(layout);
//取出簽名
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
//指向消息轉(zhuǎn)發(fā)函數(shù)
layout->invoke = (void *)cjjHook_getMsgForward(desc3->signature);
}
如果原來(lái)沒(méi)有保存過(guò)originBlock
匾浪,就深拷貝一份,拷貝完后同樣利用關(guān)聯(lián)對(duì)象技術(shù)保存起來(lái)
static NSString * const CJJBlockHookKey_OriginBlock = @"CJJBlockHookKey_OriginBlock";
static void cjjHook_setOriginBlock(CJJBlockLayout originLayout, CJJBlockLayout originLayoutCopy){
objc_setAssociatedObject((__bridge id)originLayout, CJJBlockHookKey_OriginBlock.UTF8String, (__bridge id)originLayoutCopy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static id cjjHook_getOriginBlock(CJJBlockLayout blockLayout){
return objc_getAssociatedObject((__bridge id)blockLayout, CJJBlockHookKey_OriginBlock.UTF8String);
}
取出desc3
里面的signature
(block
的簽名)賦值給_objc_msgForward
函數(shù)
// code from
// https://github.com/bang590/JSPatch/blob/master/JSPatch/JPEngine.m
// line 975
static IMP cjjHook_getMsgForward(const char *methodTypes) {
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
if (methodTypes[0] == '{') {
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodTypes];
if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
}
#endif
return msgForwardIMP;
}
讓原來(lái)的block
里的invoke
指向它卷哩,那么一旦我們調(diào)用原來(lái)的block
蛋辈,就會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制,轉(zhuǎn)發(fā)到_objc_msgForward
函數(shù)将谊,到這里我們已經(jīng)完成了一大半了冷溶,剩下的就是解析block
以及取出它的參數(shù)并且打印出來(lái)。
我們來(lái)看看被我們hook
掉的methodSignatureForSelector:
和forwardInvocation:
函數(shù)
//拿到原來(lái)block的簽名并返回
static NSMethodSignature * cjjHook_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector){
struct Block_descriptor_3 *desc3 = _Block_descriptor_3((__bridge void *)self);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:desc3->signature];
return signature;
}
static void cjjHook_forwardInvocation(id self, SEL _cmd,NSInvocation *anInvocation){
//拿到invoke指針被交換過(guò)的block結(jié)構(gòu)體(注意尊浓,這個(gè)已經(jīng)不是原來(lái)的block了)
CJJBlockLayout originLayout = (__bridge void *)anInvocation.target;
//取出我們之前保存的自定義block結(jié)構(gòu)體
CJJBlockLayout hookLayout = (__bridge void *)cjjHook_getHookBlock(originLayout);
//利用originLayout取出我們之前保存的原來(lái)的block結(jié)構(gòu)體
CJJBlockLayout originCopyLayout = (__bridge void *)cjjHook_getOriginBlock(originLayout);
//在頭文件中加了個(gè)宏定義CJJBlockHookParamsLog用于打印參數(shù)的開(kāi)關(guān)
if(CJJBlockHookParamsLog == 1){
//如果為1逞频,就打印傳入的參數(shù)
NSString *paramsString = @"";
//通過(guò)遍歷簽名的參數(shù)個(gè)數(shù),為什么從1開(kāi)始栋齿?因?yàn)閎lock的第一個(gè)參數(shù)是它自己本身苗胀,從第二個(gè)開(kāi)始才是我們傳入的參數(shù)
for (int i = 1; i < anInvocation.methodSignature.numberOfArguments; i++) {
NSString *part = [NSString stringWithFormat:@"%@",[anInvocation cjjHook_argumentAtIndex:i]];
if(!NullStr(part)){
if(NullStr(paramsString)){
paramsString = [paramsString stringByAppendingString:[NSString stringWithFormat:@"%@",part]];
}else{
paramsString = [paramsString stringByAppendingString:[NSString stringWithFormat:@",%@",part]];
}
}
}
NSLog(@"%@",paramsString);
}
//根據(jù)之前保存的position來(lái)判斷業(yè)務(wù)邏輯
NSNumber *position = cjjHook_getPosition(originLayout);
switch (position.integerValue) {
case CJJBlockHookPositionBefore:
[anInvocation invokeWithTarget:(__bridge id _Nonnull)hookLayout];
[anInvocation invokeWithTarget:(__bridge id _Nonnull)originCopyLayout];
break;
case CJJBlockHookPositionAfter:
[anInvocation invokeWithTarget:(__bridge id _Nonnull)originCopyLayout];
[anInvocation invokeWithTarget:(__bridge id _Nonnull)hookLayout];
break;
case CJJBlockHookPositionReplace:
[anInvocation invokeWithTarget:(__bridge id _Nonnull)hookLayout];
break;
case CJJBlockHookPositionDoNothing:
[anInvocation invokeWithTarget:(__bridge id _Nonnull)originCopyLayout];
break;
default:
NSLog(@"類型錯(cuò)誤");
break;
}
}
關(guān)于取參[anInvocation cjjHook_argumentAtIndex:i]
我們來(lái)看看實(shí)現(xiàn)
@implementation NSInvocation (CJJBlockHook)
// Thanks to the ReactiveCocoa team for providing a generic solution for this.
- (id)cjjHook_argumentAtIndex:(NSUInteger)index {
const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
// Skip const type qualifier.
if (argType[0] == _C_CONST) argType++;
#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
__autoreleasing id returnObj;
[self getArgument:&returnObj atIndex:(NSInteger)index];
return returnObj;
}else if (strcmp(argType, "@\"NSString\"") == 0){
NSString *val = @"";
[self getArgument:&val atIndex:(NSInteger)index];
return val;
} else if (strcmp(argType, @encode(SEL)) == 0) {
SEL selector = 0;
[self getArgument:&selector atIndex:(NSInteger)index];
return NSStringFromSelector(selector);
} else if (strcmp(argType, @encode(Class)) == 0) {
__autoreleasing Class theClass = Nil;
[self getArgument:&theClass atIndex:(NSInteger)index];
return theClass;
// Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
} else if (strcmp(argType, @encode(char)) == 0) {
WRAP_AND_RETURN(char);
} else if (strcmp(argType, @encode(int)) == 0) {
WRAP_AND_RETURN(int);
} else if (strcmp(argType, @encode(short)) == 0) {
WRAP_AND_RETURN(short);
} else if (strcmp(argType, @encode(long)) == 0) {
WRAP_AND_RETURN(long);
} else if (strcmp(argType, @encode(long long)) == 0) {
WRAP_AND_RETURN(long long);
} else if (strcmp(argType, @encode(unsigned char)) == 0) {
WRAP_AND_RETURN(unsigned char);
} else if (strcmp(argType, @encode(unsigned int)) == 0) {
WRAP_AND_RETURN(unsigned int);
} else if (strcmp(argType, @encode(unsigned short)) == 0) {
WRAP_AND_RETURN(unsigned short);
} else if (strcmp(argType, @encode(unsigned long)) == 0) {
WRAP_AND_RETURN(unsigned long);
} else if (strcmp(argType, @encode(unsigned long long)) == 0) {
WRAP_AND_RETURN(unsigned long long);
} else if (strcmp(argType, @encode(float)) == 0) {
WRAP_AND_RETURN(float);
} else if (strcmp(argType, @encode(double)) == 0) {
WRAP_AND_RETURN(double);
} else if (strcmp(argType, @encode(BOOL)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argType, @encode(bool)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argType, @encode(char *)) == 0) {
WRAP_AND_RETURN(const char *);
} else if (strcmp(argType, @encode(void (^)(void))) == 0) {
__unsafe_unretained id block = nil;
[self getArgument:&block atIndex:(NSInteger)index];
return [block copy];
} else {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(argType, &valueSize, NULL);
unsigned char valueBytes[valueSize];
[self getArgument:valueBytes atIndex:(NSInteger)index];
return [NSValue valueWithBytes:valueBytes objCType:argType];
}
return nil;
#undef WRAP_AND_RETURN
}
@end
這里用到的是ReactiveCocoa
團(tuán)隊(duì)開(kāi)源的方法襟诸,兼容各種參數(shù)類型,其中這一個(gè)
else if (strcmp(argType, "@\"NSString\"") == 0){
NSString *val = @"";
[self getArgument:&val atIndex:(NSInteger)index];
return val;
}
是我自己加進(jìn)去的基协,因?yàn)?code>NSString類型它并沒(méi)有判斷到歌亲,打印的不是我們想要的,所以我作了修改澜驮。
總結(jié):把原來(lái)的block
和自定義的block
分別保存起來(lái)陷揪,再將原來(lái)的block
指向消息轉(zhuǎn)發(fā)函數(shù),使得一調(diào)用原來(lái)的block
就啟動(dòng)消息轉(zhuǎn)發(fā)流程到達(dá)_objc_msgForward
杂穷,在這個(gè)函數(shù)我們?cè)侔阎氨4娴?code>block以及position
取出來(lái)悍缠,完成業(yè)務(wù)邏輯處理,并在調(diào)用前取出傳入的參數(shù)打印出來(lái)耐量。
//```
//- **2.實(shí)現(xiàn)下面的函數(shù)飞蚓,將`block`的實(shí)現(xiàn)修改成打印所有入?yún)ⅲ⒄{(diào)用原始實(shí)現(xiàn)**
//```
////
//比如
// void(^block)(int a, NSString *b) = ^(int a, NSString *b){
// NSLog(@"block invoke");
// }
// HookBlockToPrintArguments(block);
// block(123,@"aaa");
// //這里輸出"123, aaa"和"block invoke"
//
void HookBlockToPrintArguments(id block){
// [CJJBlockHook hookOriginBlock:block hookBlock:^{
// NSLog(@"Hello world");
// } position:CJJBlockHookPositionDoNothing];
[CJJBlockHook hookPrintParamsOriginBlock:block];
}
- (void)viewDidLoad {
[super viewDidLoad];
//第二題
void (^originBlock2)(int a, id b, NSString *string) = ^(int a, id b, NSString *string){
NSLog(@"第二題原實(shí)現(xiàn):%d-%@-%@",a,b,string);
};
HookBlockToPrintArguments(originBlock2);
originBlock2(2,@"筆",@"愛(ài)上");
}
我們?cè)谡{(diào)用HookBlockToPrintArguments(originBlock2)
后再調(diào)用originBlock2(2,@"筆",@"愛(ài)上")
廊蜒,輸出結(jié)果如下
2020-09-01 22:02:13.691192+0800 CJJBlockHook[4421:71893] 2,筆,愛(ài)上
2020-09-01 22:02:13.691283+0800 CJJBlockHook[4421:71893] 第二題原實(shí)現(xiàn):2-筆-愛(ài)上
可以看到依次打印了傳入的參數(shù)玷坠,然后才調(diào)用原始實(shí)現(xiàn),這樣就解決了第二題的需求了劲藐。
-
第三題 將所有block的實(shí)現(xiàn)修改成打印所有入?yún)吮ぃ⒄{(diào)用原始實(shí)現(xiàn)
這個(gè)比較難一點(diǎn),解法參照這里:運(yùn)行時(shí)Hook所有Block方法調(diào)用的技術(shù)實(shí)現(xiàn)
原理是利用fishhook
聘芜,谷歌開(kāi)源的框架兄渺,用于交換C函數(shù)的實(shí)現(xiàn)。
因?yàn)閷?duì)block
進(jìn)行賦值或者拷貝都會(huì)調(diào)用_Block_copy
函數(shù)汰现,所以我們可以利用fishhook
庫(kù)來(lái)將其替換成我們自定義的方法的實(shí)現(xiàn)
extern const struct mach_header* _NSGetMachExecuteHeader(void);
//聲明統(tǒng)一的block的hook函數(shù)挂谍,這個(gè)函數(shù)的定義是用匯編代碼來(lái)實(shí)現(xiàn),具體實(shí)現(xiàn)在blockhook-arm64.s/blockhook-x86_64.s中瞎饲。
extern void blockhook(void);
extern void blockhook_stret(void);
//這兩個(gè)全局變量保存可執(zhí)行程序的代碼段+數(shù)據(jù)段的開(kāi)始和結(jié)束位置口叙。
unsigned long imageTextStart = 0;
unsigned long imageTextEnd = 0;
void initImageTextStartAndEndPos()
{
imageTextStart = (unsigned long)_NSGetMachExecuteHeader();
#ifdef __LP64__
const struct segment_command_64 *psegment = getsegbyname("__TEXT");
#else
const struct segment_command *psegment = getsegbyname("__TEXT");
#endif
//imageTextEnd 等于代碼段和數(shù)據(jù)段的結(jié)尾 + 對(duì)應(yīng)的slide值。
imageTextEnd = get_end() + imageTextStart - psegment->vmaddr;
}
/**
替換block對(duì)象的默認(rèn)invoke實(shí)現(xiàn)
@param blockObj block對(duì)象
*/
void replaceBlockInvokeFunction(const void *blockObj)
{
//任何一個(gè)block對(duì)象都可以轉(zhuǎn)化為一個(gè)struct Block_layout結(jié)構(gòu)體嗅战。
struct Block_layout *layout = (struct Block_layout*)blockObj;
if (layout != NULL && layout->descriptor != NULL)
{
//這里只hook一個(gè)可執(zhí)行程序image范圍內(nèi)定義的block代碼塊妄田。
//因?yàn)閕mageTextStart和imageTextEnd表示可執(zhí)行程序的代碼范圍,因此如果某個(gè)block是在可執(zhí)行程序中被定義
//那么其invoke函數(shù)地址就一定是在(imageTextStart,imageTextEnd)范圍內(nèi)驮捍。
//如果將這個(gè)條件語(yǔ)句去除就會(huì)hook進(jìn)程中所有的block對(duì)象疟呐!
unsigned long invokePos = (unsigned long)layout->invoke;
if (invokePos > imageTextStart && invokePos < imageTextEnd)
{
//將默認(rèn)的invoke實(shí)現(xiàn)保存到保留字段,將統(tǒng)一的hook函數(shù)賦值給invoke成員东且。
int32_t BLOCK_USE_STRET = (1 << 29); //如果模擬器下返回的類型是一個(gè)大于16字節(jié)的結(jié)構(gòu)體启具,那么block的第一個(gè)參數(shù)為返回的指針,而不是block對(duì)象珊泳。
void *hookfunc = ((layout->flags & BLOCK_USE_STRET) == BLOCK_USE_STRET) ? blockhook_stret : blockhook;
if (layout->invoke != hookfunc)
{
layout->descriptor->reserved = (uintptr_t)layout->invoke;
layout->invoke = hookfunc;
//打印參數(shù)
[CJJBlockHook hookPrintParamsOriginBlock:(__bridge id)layout];
}
}
}
}
void * (*old_Block_copy)(const void *aBlock);
void *my_Block_copy(const void *aBlock){
struct Block_layout *block;
if(!aBlock) return NULL;
block = (struct Block_layout *)aBlock;
replaceBlockInvokeFunction(block);
return old_Block_copy(block);
}
//所有block調(diào)用前都會(huì)執(zhí)行blockhookLog,這里的實(shí)現(xiàn)就是簡(jiǎn)單的將block對(duì)象的函數(shù)符號(hào)打印出來(lái)鲁冯!
void blockhookLog(void *blockObj)
{
struct Block_layout *layout = blockObj;
//注意這段代碼在線上的程序是無(wú)法獲取到符號(hào)信息的拷沸,因?yàn)榫€上的程序中會(huì)刪除掉所有block實(shí)現(xiàn)函數(shù)的符號(hào)信息。
Dl_info dlinfo;
memset(&dlinfo, 0, sizeof(dlinfo));
if (dladdr((const void *)layout->descriptor->reserved, &dlinfo))
{
// NSLog(@"%s be called with block object:%@", dlinfo.dli_sname, blockObj);
//打印入?yún)?// [CJJBlockHook hookPrintParamsOriginBlock:(__bridge id)layout];
}
}
具體調(diào)用方法如下
+ (void)fishHook{
//初始化并計(jì)算可執(zhí)行程序代碼段和數(shù)據(jù)段的開(kāi)始和結(jié)束位置薯演。
initImageTextStartAndEndPos();
struct rebinding rebns[1] = {"_Block_copy",my_Block_copy,(void **)&old_Block_copy};
rebind_symbols(rebns, 1);
}
其中還用到了匯編撞芍。。涣仿。這里的實(shí)現(xiàn)流程可以直接閱讀這位大神寫的文章,我就不再贅述了示惊。
//```
//- **3.實(shí)現(xiàn)下面的函數(shù)好港,使得調(diào)用這個(gè)函數(shù)之后,后面創(chuàng)建的任意`block`都能自動(dòng)實(shí)現(xiàn)第二題的功能**
//```
void HookEveryBlockToPrintArguments(void){
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[CJJBlockHook fishHook];
});
}
//``
- (void)viewDidLoad {
[super viewDidLoad];
//第三題
HookEveryBlockToPrintArguments();
void (^originBlock3)(NSString *str, int num, CGFloat meter) = ^(NSString *str, int num, CGFloat meter){
NSLog(@"第三題原實(shí)現(xiàn):%@-%d-%f",str,num,meter);
};
originBlock3(@"呵呵",33,4.0);
}
我們?cè)谡{(diào)用HookEveryBlockToPrintArguments()
后再調(diào)用任意的block米罚,這里是originBlock3(@"呵呵",33,4.0)
钧汹,輸出結(jié)果如下
2020-09-01 22:02:13.699379+0800 CJJBlockHook[4421:71893] 呵呵,33,4
2020-09-01 22:02:13.699554+0800 CJJBlockHook[4421:71893] 第三題原實(shí)現(xiàn):呵呵-33-4.000000
可以看到之后任意一個(gè)我們定義的block
的調(diào)用都會(huì)先打印了傳入的參數(shù),然后才調(diào)用原始實(shí)現(xiàn)录择,第三題解決拔莱!
demo在這里->CJJBlockHook
我把這些方法都封裝到CJJBlockHook
里面,僅供大家學(xué)習(xí)隘竭,暫時(shí)不建議商用塘秦,喜歡的點(diǎn)個(gè)贊支持一下,或者你有更好的想法歡迎issue
我动看,共同探討學(xué)習(xí)尊剔!
·