什么是block

block是带有局部变量的匿名函数

  • 局部变量:作用域仅在block内的变量, 由block持有

block语法

^(返回值)(参数列表){表达式}

ex:
  ^(int)(string s1, string s2) {
       log("%s, %s", s1, s2)
  }

其中 ,返回值类型可以省略,参数若为空,则参数列表也可以省略

//这是最简单的block结构
^{表达式}

block 的三种类型

  1. 全局block NSGlobalBlock
  2. 栈上block NSStackBlock
  3. 堆上block NSMallocBlock

三种block的主要区别在于其生命周期和作用域

全局block

全局block作用于全局 ,随应用进程的结束而结束,并且无法对外部变量进行捕获

栈block

栈上block作用于当前声明block变量的函数中,函数返回,block声明周期结束,可以捕获局部变量,静态局部变量会以指针的形式捕获。在ARC中,一旦栈block捕获变量,则会变成堆block

堆block

堆上block则是作用在堆上,由ARC对其进行生命周期的管理,MRC下只有进行copy后才会将block迁移至堆区

block的变量捕获

  • 基本数据类型的局部变量 Block 可以截获其值
  • 对于对象类型的局部变量连同所有权修饰符一起截获
  • 局部静态变量以指针的形式进行截获
  • 全局变量和静态全局变量,block 是不截获的

下面来看一下代码

int global_var = 10;

void(^global_blk)(void) = ^{
    printf("sss: %d\n", global_var);
};
void(^stack_blk)(void);

int main() {
    //c语言中,block无法捕获数组字面量,此处用指针替代
    char *local_var = "local_var";
    static char *local_static_var = "local_static_var";
    
    stack_blk = ^{
        printf("global_var: %d\nlocal_var: %s\nlocal_static_var: %s\n", global_var, local_var, local_static_var);
    };
    
    global_blk();
    stack_blk();
    global_var = 20;
    local_var = "local_var_change";
    local_static_var = "local_static_var_change";
    global_blk();
    stack_blk();
    
    return  0;
}

上面代码中,定义了一个全局变量global_var,一个全局的block,并声明了一个block变量来承载一个栈block (OC在ARC环境下栈block会被自动copy进堆区,所以这里使用了c语言),main函数里定义两个变量,分别是局部变量和静态局部变量,并在两次block调用期间,修改了一次变量信息

上述代码运行结果:

sss: 10
global_var: 10
local_var: local_var
local_static_var: local_static_var
sss: 20
global_var: 20
local_var: local_var
local_static_var: local_static_var_change

从结果可以看出,全局变量在被修改了数据后,block内的值也受到了影响而改变。而局部变量和局部静态变量被修改了数据后,则表现得有些不一样。

可以看到,名字为local_var的局部变量,在外部被修改了值以后,在block内部依旧打印出了原来的值,而名字为local_static_var的静态全局变量的值则是内外一同发生了改变,这是为什么?

借由clang可以把文件编译成oc的cpp实现,我们来试一下
控制台键入命令

//我的文件名是main.c
clang -rewrite-objc main.c

去掉一些自动生成的冗余代码,剩余代码如下,有些长,可以先跳过

int global_var = 10;


struct __global_blk_block_impl_0 {
  struct __block_impl impl;
  struct __global_blk_block_desc_0* Desc;
  __global_blk_block_impl_0(void *fp, struct __global_blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __global_blk_block_func_0(struct __global_blk_block_impl_0 *__cself) {

    printf("sss: %d", global_var);
}

static struct __global_blk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __global_blk_block_desc_0_DATA = { 0, sizeof(struct __global_blk_block_impl_0)};
static __global_blk_block_impl_0 __global_global_blk_block_impl_0((void *)__global_blk_block_func_0, &__global_blk_block_desc_0_DATA);
void(*global_blk)(void) = ((void (*)())&__global_global_blk_block_impl_0);
void(*stack_blk)(void);


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  char *local_var;
  char **local_static_var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_local_var, char **_local_static_var, int flags=0) : local_var(_local_var), local_static_var(_local_static_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  char *local_var = __cself->local_var; // bound by copy
  char **local_static_var = __cself->local_static_var; // bound by copy

        printf("global_var: %d\n local_var: %s\n local_static_var: %s\n", global_var, local_var, (*local_static_var));
    }

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() {

    char *local_var = "local_var";
    static char *local_static_var = "local_static_var";
    ((void (*)(__block_impl *))((__block_impl *)global_blk)->FuncPtr)((__block_impl *)global_blk);
    stack_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_var, &local_static_var));
    ((void (*)(__block_impl *))((__block_impl *)stack_blk)->FuncPtr)((__block_impl *)stack_blk);
    return 0;
}

先来看一下为什么某些变量在block外部被修改,而变量在block内部的值不变

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  char *local_var;
  char **local_static_var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_local_var, char **_local_static_var, int flags=0) : local_var(_local_var), local_static_var(_local_static_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这是代表着我们栈block的结构体,可以看到,结构体内部有两个变量,名字分别是我们在使用中定义的名字,这两个变量就是在block执行过程中,被copy进来的,但是有一些区别,local_var是以变量类型进行捕获,而静态局部变量local_static_var则是以指针的形式进行捕获。

这也就意味着,在我修改静态局部变量时,修改的是其引用,所以不受影响

关于__block

iOS block什么时候为stack_局部变量


有没有发现,当我在block内部修改一个局部变量的时候,会报出一个编译错误,意思是要修改的变量需要一个__block修饰符,我们先加一个修饰符给报错的变量

iOS block什么时候为stack_iOS_02

但是,为什么上那个名字是local_static_var的静态局部变量没事儿呢,我们先来看一下clang后的结果

struct __Block_byref_local_var_0 {
  void *__isa;
__Block_byref_local_var_0 *__forwarding;
 int __flags;
 int __size;
 char *local_var;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  char **local_static_var;
  __Block_byref_local_var_0 *local_var; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char **_local_static_var, __Block_byref_local_var_0 *_local_var, int flags=0) : local_static_var(_local_static_var), local_var(_local_var->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我们可以发现,多了一个叫做__Block_byref_local_var_0的结构体,这个结构体就是被我们用__block修饰后的那个变量。
而在我们原来的block结构体中,多了一个__Block_byref_local_var_0 *local_var; 变量,这个变量就是对__block修饰后变量的引用,通过修改引用就可以来修改被block捕获的变量。
通过之前的代码分析得到静态局部变量在block结构体中也是以引用的形式存在的,所以,block内部修改静态局部变量不会引发编译错误