iOS-玩轉(zhuǎn)Block(Hook Block 交換block的實(shí)現(xiàn))

前方極其燒腦漏策,建議->點(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è)大神默默分享灭忠,比如BlockHookfishhook座硕,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ì)講到。

保存自定義的blockCJJBlockHookPositionBefore报慕,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)的blockinvoke指針指向_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里面的signatureblock的簽名)賦值給_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í)尊剔!
·

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菱皆,隨后出現(xiàn)的幾起案子须误,更是在濱河造成了極大的恐慌,老刑警劉巖仇轻,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件京痢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡篷店,警方通過(guò)查閱死者的電腦和手機(jī)祭椰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)疲陕,“玉大人吭产,你說(shuō)我怎么就攤上這事⊙悸郑” “怎么了臣淤?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窃爷。 經(jīng)常有香客問(wèn)我邑蒋,道長(zhǎng)姓蜂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任医吊,我火速辦了婚禮钱慢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卿堂。我一直安慰自己束莫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布草描。 她就那樣靜靜地躺著览绿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穗慕。 梳的紋絲不亂的頭發(fā)上饿敲,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音逛绵,去河邊找鬼怀各。 笑死,一個(gè)胖子當(dāng)著我的面吹牛术浪,可吹牛的內(nèi)容都是我干的瓢对。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胰苏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沥曹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起碟联,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妓美,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鲤孵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體壶栋,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年普监,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贵试。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凯正,死狀恐怖毙玻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情廊散,我是刑警寧澤桑滩,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站允睹,受9級(jí)特大地震影響运准,放射性物質(zhì)發(fā)生泄漏幌氮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一胁澳、第九天 我趴在偏房一處隱蔽的房頂上張望该互。 院中可真熱鬧,春花似錦韭畸、人聲如沸宇智。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)随橘。三九已至,卻和暖如春隘马,著一層夾襖步出監(jiān)牢的瞬間太防,已是汗流浹背妻顶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工酸员, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讳嘱。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓幔嗦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親沥潭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邀泉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容