Blocks原理

博客连接Blocks原理ios

更新日期:2019-07-22面试

Block的实质

咱们先写一个最基础的block编程

int main(int argc, const char * argv[]) {
    void (^testBlock)(void) = ^{
        printf("asddasd");
    };
    testBlock();
    
    return 0;
}
复制代码

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转化成C++代码。其中有关键代码以下:api

int main(int argc, char * argv[]) {
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
复制代码

将代码简化一下:bash

int main(int argc, char * argv[]) {
    // 定义block变量
    void (*testBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
    // 执行block
    testBlock->FuncPtr(testBlock);
    
    return 0;
}
复制代码

能够看出咱们定义的testBlock变成了&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA),执行block的时候将testBlock()变成了testBlock->FuncPtr(testBlock)框架

__main_block_impl_0是一个结构体,相关定义以下:iphone

struct __block_impl {
    void *isa; // isa是OC对象特有的标志
    int Flags;
    int Reserved;
    void *FuncPtr; // 该指针指向block执行函数的地址
};

static struct __main_block_desc_0 {
    size_t reserved;   // 预留的字段
    size_t Block_size; // block所占内存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// __main_block_impl_0是一个C++结构体能够声明函数
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    // 没有返回类型,与结构体同名的函数,这样的函数称为构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock; // 初始化__block_impl结构体的isa成员变量
        impl.Flags = flags;
        impl.FuncPtr = fp; // fp就是__main_block_func_0函数
        Desc = desc;
    }
};

// 该函数封装了block的执行代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("asddasd");
}
复制代码

__main_block_impl_0有两个成员变量,分别是__block_impl impl__main_block_desc_0* Desc,还有一个__main_block_impl_0的构造函数。__block_impl__main_block_desc_0也是两个结构体,其成员变量做用都写在代码注释中,这里就再也不说了。函数

最重要的是其构造函数:测试

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}
复制代码

在定义block变量的时候,传入的__main_block_func_0&__main_block_desc_0_DATA分别对应了fp* descfp指向的就是__main_block_func_0函数,它会传给impl.FuncPtr,另外的isa的初始值是_NSConcreteStackBlock_NSConcreteStackBlock就至关于class_t中的结构体类型的。优化

接着看调用部分,关于block的调用会被转化成testBlock->FuncPtr(testBlock),它指向的是__main_block_func_0函数的地址。这里要注意testBlock->FuncPtr(testBlock)是我将强制转化取消后的代码,本来应该是(__block_impl *)testBlock->FuncPtr(testBlock),由于__block_impl imp__main_block_impl_0这个结构体的第一个成员变量,因此__block_impl imp的内存地址就是__main_block_impl_0结构体的内存地址。

因此说block的本质就是Objective-C对象,block的调用就是函数指针的调用

截获自动变量值

根据《Objective-C高级编程》一书中提到,所谓的“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量被保存到Block的结构体实例(即Block自身中)。

那不一样类型变量之间的截获会有区别吗,使用如下代码:

#include <stdio.h>

static int globalStaticValue = 1;

int globalValue = 2;

int main(int argc, char * argv[]) {
    int a = 3;
    
    static int b = 4;
    
    void (^testBlock)(void) = ^{
        printf("%d", globalStaticValue);
        printf("%d", globalValue);
        printf("%d", a);
        printf("%d", b);
    };
    
    testBlock();
    
    return 0;
}
复制代码

转化成C++代码:

static int globalStaticValue = 1;
int globalValue = 2;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    int *b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int *b = __cself->b; // bound by copy
    
    printf("%d", globalStaticValue);
    printf("%d", globalValue);
    printf("%d", a);
    printf("%d", (*b));
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, char * argv[]) {
    int a = 3;
    
    static int b = 4;
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
复制代码

从上面的代码能够看出:

  • 局部变量会被block直接截获
  • 局部静态变量会被block直接截获其指针,经过指针进行方法
  • 全局变量和全局静态变量并不会被截获,而是直接使用

截获对象

block是如何截获对象变量的,使用以下代码:

#import <Foundation/Foundation.h>

static NSObject *globalObjc;

int main(int argc, char * argv[]) {    
    NSObject *object = [[NSObject alloc] init];
    __weak NSObject *weakObject = object;
    
    void (^testBlock)(void) = ^{
        NSLog(@"%@", object);
        NSLog(@"%@", weakObject);
        NSLog(@"%@", globalObjc);
    };
    
    testBlock();
    
    return 0;
}
复制代码

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m命令,转化成C++代码后:

static NSObject *globalObjc;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    NSObject *__strong object;
    NSObject *__weak weakObject;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _object, NSObject *__weak _weakObject, int flags=0) : object(_object), weakObject(_weakObject) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSObject *__strong object = __cself->object; // bound by copy
    NSObject *__weak weakObject = __cself->weakObject; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_0, object);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_1, weakObject);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_2, globalObjc);
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    NSObject *object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    __attribute__((objc_ownership(weak))) NSObject *weakObject = object;

    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, object, weakObject, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);

    return 0;
}
复制代码

若是截获的变量是对象类型的,会将对象变量及其全部权修饰符一并截获。再看一下__main_block_copy_0__main_block_dispose_0

  • 当block进行一次copy操做的时候,__main_block_copy_0函数内部调用_Block_object_assign函数,它根据对象的类型产生强引用或者弱引用;

  • 当block从堆中移除的时候,__main_block_dispose_0函数内部调用_Block_object_dispose函数,它会自动释放掉引用的变量。

    关于截获对象的实验是在ARC环境下执行的,testBlock是_NSConcreteMallocBlock类型的。若是在非ARC环境下,testBlock是_NSConcreteStackBlock类型的即Block在栈上,则不会对截获的对象变量进行强引用

__block修饰符

当咱们须要对被Block截获的局部变量进行赋值操做的话,须要添加一个__block这个说明符,那__block到底有什么做用呢?

先看一段代码:

#include <stdio.h>

int main(int argc, char * argv[]) {
    __block int a = 1;
    void (^testBlock)(void) = ^{
        a = 100;
        printf("%d", a);
    };
    testBlock();
    
    return 0;
}
复制代码

因为使用了__block关键字,能够修改变量a的值,因此输出结果是100。

先说明一下为何不使用__block就不能修改值。执行block就是调用__main_block_func_0函数,amain函数中的局部变量,咱们不可能在一个函数中去修改另外一个函数的局部变量。

将上面代码转换成C++代码,先看一下__main_block_impl_0这个结构体

struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
复制代码

__main_block_impl_0结构体中多了一个__Block_byref_a_0 *a而不是int a__Block_byref_a_0也是一个结构体,里面也有一个isa指针,所以咱们也能够将它当作一个对象。另外还有一个__Block_byref_a_0 *类型的__forwarding指针和变量a。关于__forwarding指针的做用咱们会在后面提到。

接着是主函数:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
复制代码

在主函数中咱们能够看到__block int a = 1;转化成了__Block_byref_a_0的一个结构体,其中__forwarding指针指向这个结构体本身。

访问__block变量

最后是Block执行的时候即调用__main_block_func_0函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  (a->__forwarding->a) = 100;
  printf("%d", (a->__forwarding->a));
}
复制代码

原来的a = 100;则被转换成__Block_byref_a_0 *a = __cself->a;(a->__forwarding->a) = 100;。这两句代码的意思是先获取结构体中的a,经过a结构体的__forwarding指针指向成员变量a赋值为100。

因此__block的本质就是**__block将变量包装成一个对象,将截获到的值存在这个对象中,经过对截获的值进行赋值而更改原有的值**。

Block存储域

block有下面三种类型

设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的数据区域
_NSConcreteMallocBlock

那么如何肯定block的类型?下面这个实验须要在MRC环境下进行测试,代码以下:

int main(int argc, char * argv[]) {
    int a = 1;
    
    void (^block1)(void) = ^{
        NSLog(@"%s - %d", __func__, a);
    };
    
    void (^block2)(void) = ^{
        NSLog(@"%s", __func__);
    };
    
    void (^block3)(void) = [block1 copy];
    
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [block3 class]);
    
    return 0;
}
复制代码

结果以下:

block类型

经过打印block的类型能够知道

环境
_NSConcreteStackBlock 访问了自动变量值
_NSConcreteGlobalBlock 没有访问自动变量值
_NSConcreteMallocBlock _NSConcreteStackBlock使用了copy

copy操做对不一样类型block的影响:

Block的类 副本源的配置存储域 复制效果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么也不作
_NSConcreteMallocBlock 引用计数增长

block的copy操做能保证block不容易被销毁。可是在ARC环境下,编译器会根据状况自动将栈上的block复制到堆上,即咱们打印出来的block是_NSConcreteMallocBlock类型的。

这些状况有:

  • block做为返回值的时候;
  • block赋值给strong指针的时候;
  • block做为Cocoa API中方法名含有usingBlock的方法参数的时候;
  • block做为GCD API的参数的时候;

因此若是将上面代码放在ARC环境下执行的话,则block1__NSMallocBlock__类型的。

release操做会在其内部自动执行。

__block变量的存储域

__block变量的存储域 Block从栈复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

既然__block对应的结构体或者对象是在Block内部使用,那么Block就须要对这个对象的生命周期进行负责。接着咱们从源码里面进行理解

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
复制代码

咱们须要注意两个函数__main_block_copy_0__main_block_dispose_0

  • 当block进行一次copy操做的时候,__main_block_copy_0函数内部调用_Block_object_assign函数,它会对__block变量生成强引用;
  • 当block从堆中移除的时候,__main_block_dispose_0函数内部调用_Block_object_dispose函数,它会自动释放掉引用的__block变量。

上面的代码是在ARC环境下的,若是Block在栈上,并不会对__block变量产生引用

这里还要解决一个前面遗留的问题,既然__Block_byref_a_0结构体中已经有变量a,为何还须要使用__forwarding指针对a赋值呢或者说__forwarding存在的意义是什么?

关于__forwarding的做用《Objective-C高级编程》一书中的第111页、第112页中有很明确的说到:__block变量的结构体成员变量__forwarding能够实现不管__block变量配置在栈上仍是堆上都可以正确地访问__block变量。

没有进行copy操做的时候:

栈上的block
进行copy操做之后:
block拷贝

这里分为两种状况:

  1. 若是咱们没有对栈上的Block执⾏copy操做,修改被__block修饰的变量其实是经过其__forwarding指针指向的自身,对其中的变量进行修改。

  2. 若是咱们执行过copy操做,那么栈上的Block的__forwarding指针,实际是指向堆上的__block修饰的变量,⽽堆上的__forwarding指针则指向⾃自身的__block修饰的变量。在变量做用域结束的时候,栈上的__block变量和Block被废弃掉,可是堆上的__block变量和Block不受影响。

Block的循环引用

咱们在使用Block的时候,若是有一个对象持有了这个Block,而在Block内部又使用了这个对象,就会形成循环引用。如如下写法:

#import <Foundation/Foundation.h>

typedef void (^TestBlock) (void);

@interface AObject: NSObject {
    TestBlock testBlock;
}

@end

@implementation AObject

- (instancetype)init {
    if (self = [super init]) {
        testBlock = ^{
            NSLog(@"self = %@", self);
        };
    }
    
    return self;
}

- (void)dealloc {
    NSLog(@"%@ dealloc", self.class);
}

@end

int main(int argc, char * argv[]) {
    
    AObject *a = [[AObject alloc] init];
    NSLog(@"%@", a);
    
    return 0;
}

复制代码

能够看到,上面这段代码发生了循环引用,致使AObject对象没法被释放。一般作法是使用__weak关键字在Block外部声明一个弱引用。从新修改下init方法:

- (instancetype)init {
    if (self = [super init]) {
        __weak AObject *weakSelf = self;
        testBlock = ^{
            NSLog(@"self = %@", weakSelf);
        };
    }
    
    return self;
}
复制代码

前面在讲述截获自动变量值的时候,咱们知道Block在截获对象类型的时候,会连同对象的全部权修饰符一块儿截获,这里截获的是__weak修饰的weakSelf,所以不会发生循环引用。

在为避免循环引用而使用__weak修饰符的时候,还要注意有可能在Block执行的时候,对象在中途被释放掉了。这个时候须要在Block内部声明一个局部变量强持有对象,这个局部变量会在到Block执行结束时自动释放,不会形成循环引用,而对象也会在Block执行结束后被释放。

是否是全部的Block都须要在外部声明使用__weak修饰呢?答案是否认的。所谓“引用循环”是指双向的强引用,因此那些“单向的强引用”(block强引用self)没有问题。例如:

[UIView animateWithDuration:duration
                 animations:^{
                     [self.superview layoutIfNeeded];
                 }];
复制代码

但若是你使用一些参数中可能含有ivar的系统的api,如NSNotificationCenter就要当心一点。

__weak __typeof__(self) weakSelf = self;
 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                               object:nil
                                                                queue:nil
                                                           usingBlock:^(NSNotification *note) {
    //使用self会循环引用,可是使用weakSelf或者strongSelf则没有问题                                                      
     __typeof__(self) strongSelf = weakSelf;
     [strongSelf dismissModalViewControllerAnimated:YES];
 }];
复制代码

能够看出self --> _observer --> block --> self这显然是一个循环引用。总而言之,只有当self直接或间接的持有 Block,而且在Block内部又使用了self的时候,才应该使weakSelf

总结

  1. block的本质是一个封装了函数调用以及函数调用环境的OC对象;
  2. block截获自动变量值的规则:
    1. 局部变量会被直接截获;
    2. 局部静态变量会被截获其指针;
    3. 全局变量并不会被截获,而是直接使用;
  3. block截获对象的规则:
    1. block位于栈上,则不会对截获的对象变量进行强引用;
    2. block从栈上复制到堆上,调用copy函数,对截获的变量进行强/弱引用;
    3. block从堆上移除,调用dispose函数,自动释放引用的变量;
  4. block使用copy属性的缘由:在MRC下,访问了自动变量的block处于栈上,容易被释放,使用copy能够将其复制到堆上,放在堆上即可以本身去控制Block的生命周期;在ARC下,对Block作了优化;
  5. block的循环引用:一个对象持有了这个Block,而在Block内部又使用了这个对象,就会形成循环引用;
  6. __block的做用:解决block内部没法修改自动变量值的问题;
  7. __block的注意点:不能修饰全局变量和静态变量;
  8. __block的实质:编译器将__block变量包装成一个对象,将截获到的值存在这个对象中,经过对截获的值进行赋值而更改原有的值;
  9. __forwarding指针的做用:能够实现不管__block变量配置在栈上仍是堆上都可以正确地访问__block变量;

Sunnyxx的Block面试题

sunnyxx_block

代码以下:

#import <Foundation/Foundation.h>
#import "fishhook.h"

// Block定义
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
};

#pragma mark - 第一题

static void test_1__main_block_func_0(struct __main_block_impl_0 *__cself){
    NSLog(@"Hello,world!");
}

void HookBlockToPrintHelloWorld(id block){
    //    struct __block_impl *tmp = (__bridge struct __block_impl *)block;
    //    tmp->FuncPtr = &test_1__main_block_func_0;
    // 或者
    struct __main_block_impl_0 *tmp2 = (__bridge struct  __main_block_impl_0 *)block;
    ((struct __block_impl *)tmp2)->FuncPtr = &test_1__main_block_func_0;
}

#pragma mark - 第二题

static void (*original_func)(struct __main_block_impl_0 *, int, NSString *);

static void test_2__main_block_func_0(struct __main_block_impl_0 *__cself, int a, NSString *b) {
    NSLog(@"%d %@", a, b);
    original_func(__cself, a, b);
}

void HookBlockToPrintArguments(id block) {
    struct __block_impl *tmp = (__bridge struct __block_impl *)block;
    original_func = tmp->FuncPtr;
    tmp->FuncPtr = &test_2__main_block_func_0;
}

#pragma mark - 第三题

static id (*original__Block_copy)(id);

id new__Block_copy(id block) {
    id tmp = original__Block_copy(block);
    HookBlockToPrintArguments(tmp);
    
    return tmp;
}

void HookEveryBlockToPrintArguments() {
    struct rebinding open_rebinding = {
        "_Block_copy",
        new__Block_copy,
        (void *)&original__Block_copy
    };
    
    rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
}

int main(int argc, char * argv[]) {
    // 第一题
    void(^block1)(void) = ^{
        NSLog(@"%s", __func__);
    };
    
    HookBlockToPrintHelloWorld(block1);
    
    block1();
    
    // 第二题
    void (^block2)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block invoke");
    };
    
    HookBlockToPrintArguments(block2);
    
    block2(123, @"aaa");
    
    // 第三题
    HookEveryBlockToPrintArguments();
    
    void (^block3)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block3 invoke");
    };
    
    block3(1, @"dsadas");
    
    void (^block4)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block4 invoke");
    };
    
    block4(222, @"ddd");
    
    return 0;
}
复制代码

第一题分析

想要替换原有的block实现,咱们须要知道Block是如何执行代码的。经过上面的原理分析,咱们知道在执行block的代码就至关于调用__main_block_func_0函数。该函数指针被保存在__block_impl结构体的FuncPtr中。咱们知道将FuncPtr指向咱们本身的函数就能够完成切换。

第二题分析

知道第一题如何解答以后,第二题相对来讲简单一点,其实就是在咱们自定义函数的时候,这个函数须要包括咱们传入的参数,以及原有实现。

那么经过一个函数指针能够保存原有实现,在自定义函数中经过函数指针调用原有函数便可,而参数是自己__main_block_func_0函数就有的,咱们只要确保咱们自定义函数的参数列表与__main_block_func_0函数的参数列表一致就行。

第三题分析

这道题目说白了就是在一个适当的时机去调用HookBlockToPrintArguments。经过fishhook这个框架,咱们能够动态的修改C语言函数。那如何肯定这个C语言函数呢?这个函数须要有这么一个功能,它能够拿到原来的block对象,通过替换再从新返回。

在ARC环境下,编译器会根据状况自动将栈上的block复制到堆上,它会调用一个copy函数即_Block_copy,因此咱们须要动态修改这个函数。

相关文章
相关标签/搜索