1、Block基本信息

(1)什么是Block?
Blocks是C语言的扩充功能,即:带有自动变量(局部变量)的匿名函数。
Block本质是Objective-C对象
(2)Block语法

^<返回值类型><参数列表><表达式>;

  1. ^int(int count) {return count + 1;};

Blocks中void可以省略简写

  1. ^void(void){ print('xxxx'); };
  2. //等价于
  3. ^{print('xxxx');};


(3)Block类型变量**

语法: <返回值类型>(^<命名>)(<参数>) 示例:int (^Block)(int) typedef int (^Block)(int);


(4)Block对局部变量的捕获**

int a = 10;  
void(^blk)(void) = ^{
    printf("a = %d \n",a);
};
a = 20;
blk();

输出结果

a = 10

可以看出a的值并没有因为a=20改变,在执行blk()时而改变,这就是Block对局部变量的捕获
(5)__block修饰符
block可以解决局部变量在block内被赋值,如果不使用block则直接赋值会出现编译错误。
(6)Block的实质探索
将下面代码进行实质转换

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void)=^{ printf("block\n"); };
        block();
    }
}

使用clang转化之后

struct __block_impl {
  void *isa;
  int Flags; //标示
  int Reserved; //版本升级所需区域
  void *FuncPtr; //函数指针
};

// 结构体的声明
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指针赋值给Block结构体成员变量isa
    impl.Flags = flags; 
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 参数__cself是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 printf("block\n"); }

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*block)(void)=
            ((void (*)())&__main_block_impl_0(
                (void *)__main_block_func_0, 
                &__main_block_desc_0_DATA));
        //方法调用
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        //将转化为(*block->impl.FuncPtr)(blk)

    }
    return 0;
}

从上面转化的代码中可以看出Block最终转化为C语言结构体,出现__main_block_impl_0__main_block_func_0__main_block_desc_0__block_impl
__main_block_impl_0结构体相当于objc_object结构体的OC类对象的结构体。

我们在block中调用局部变量(自动变量)时

int a = 10;
void(^block)(void)=^{ printf("block===%d\n",a);};
block();

通过clang之后,底层源码中__main_block_impl_0 中添加了成员变量a

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //新添加的成员变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

从上述代码中,基本可以解释了,局部变量在block中被捕获的原因是block的底层结构体中添加了新的成员变量来接受外部的局部变量,所以在block执行的时候,实际就是赋值时的值。
Block不能直接使用C语言数组类型的自动变量
在Block中使用C语言字符数组时,会发生编译报错。

const char text[] = "hello";
void(^block)(void)=^{ printf("block===%c\n",text[2]);};
block();

报错信息:

Cannot refer to declaration with an array type inside block

__block说明符
我们知道在block中给局部变量进行赋值时,直接赋值则会引发编译错误。如果我们加上block则就能在block中进行赋值,以下我们探究下添加block之后,底层做了些什么?

 __block int a = 10;
void(^block)(void)=^{
    a = a + 10;
    printf("block===%d\n",a);
};
block();

我们使用clang编译之后探究一下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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;
  }
};
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) = (a->__forwarding->a) + 10;
            printf("block===%d\n",(a->__forwarding->a));
        }
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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // __block int a = 10;
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        //   void(^block)(void)=^{ a = a + 10; printf("block===%d\n",a);};               
        void(*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
         // block();                  
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从以上的代码中可以看出加上__block修饰符之后,底层源码中出现结构体__Block_byref_a_0

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

__main_block_impl_0中持有__Block_byref_a_0的指针a,而__Block_byref_a_0的结构体实例成员变量__forwarding持有指向该实例自身的指针。
关于Block中的全局变量,局部变量,静态变量的探索可以阅读[*Block常见问题
](https://www.yuque.com/zhangyanneng/angtqk/alhb5e)

2、Block存储域

名称 实质
Block 栈上Block的结构体实例
__block 栈上__block变量的结构体实例

Block的类所处的存储域

设置对象的存储域
_NSConcreteStackBlock 栈区
_NSConcreteGlobalBlock 全局区(.data区)
_NSConcreteMallocBlock 堆区

Objective-C中的内存分区
在之前的案例中impl.isa = &_NSConcreteStackBlock;都是指向栈区的。那么全局预期的的在什么场景下出现呢?看下面的代码

// 全局的blk
void(^gblk)(void) = ^{ printf("global block \n");};


int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
}

通过clang这段代码之后,我们发现impl.isa = &_NSConcreteGlobalBlock;

struct __gblk_block_impl_0 {
  struct __block_impl impl;
  struct __gblk_block_desc_0* Desc;
  __gblk_block_impl_0(void *fp, struct __gblk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

以上说明,全局的block的存储区域是在全局区上的。

将block作为函数返回时,编译器会自动的生成复制到堆上的代码,栈上的block会自动调用copy方法复制到堆上。实际在使用中,我们也常用copy关键字作为block的属性,或者手动复制到堆上。
Block在在使用copy方法后的行为关系

Block的类 副本源的配置存储域 copy复制效果
_NSConcreteStackBlock 从栈复制到堆上
_NSConcreteGlobalBlock 全局区 什么也不做
_NSConcreteMallocBlock 引用计数加1

Block从栈复制到堆上时对__block变量产生的影响

__block的存储域 Block从栈上复制到堆上的影响
从栈上复制打堆上并被Block持有
被Block持有

在Block中使用block变量时,则当该Block从栈上复制到堆上时,使用的所有block变量必定被配置在栈上,同时这些变量也将会全部从栈上复制到堆上。如果block变量已经在堆上则不会影响,只会被Block所持有,并增加引用计数。**栈上的block变量结构体实例在从栈上复制到堆上时,实际是将成员变量forwarding指针的值替换为复制到目标堆上的block变量用结构体实例的地址。**通过这个特性,无论在Block中,还是Block外,还是栈/堆中,都能正确的访问同一个block变量。
当配置到堆上的Block被释放的时候,其所持有的
block变量也将被释放,引用计数减1。

捕获对象时出现的copy和dispose
在__main_block_desc_0结构体中出现的copy和dispose成员变量,如下示例:

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};

出现copy和dispose两个成员变量的原因是Objective-C为了准确的把握Block从栈复制到堆以及堆上的Block进行释放的时机。以及在Block结构体中使用strong修饰符和weak修饰符的变量,也可以在恰当的时机进行初始化和释放。处了copy和dispose两个成员变量,还有作为指针赋值给这两成员变量的main_block_copy_0函数和main_block_dispose_0函数。

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

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

_Block_object_assign函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
_Block_object_dispose函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体中的对象。

调用copy函数和dispose函数的时机

函数 调用时机
copy 函数 栈上的Block复制到堆上时
dispose 函数 堆上的Block被废弃时

BLOCK_FIELD_IS_BYREF = 8 对应的是__block变量
BLOCK_FIELD_IS_OBJECT = 3 对应的是对象
**
那么什么时候栈上的Block会复制到堆上呢?

  • 调用Block的copy实例方法时
  • Block作为函数返回值时
  • 将Block赋值给__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

Block复制过程归结于都是自动调用了_Block_copy函数

Block常见问题