Objective-C之Block二三事

Block是iOS4引入的功能,灵活运用Block可使代码简洁易读,本文通过Block是什么、Block和函数的区别以及__block的意义等方面展开讲解。

一、Block是什么

Block是能自动截取自动变量的匿名函数,其本质是Objective-C的对象。想要了解Block的本质需要借助clang 的-rewrite-objc(将Objective-C代码转换成C++)命令,假设Objective-C的Block代码如下:

#include"stdio.h"
int main()
{    
    int val = 10;
        void (^blk)(void) = ^{
    printf("val = %d\n",val);
    };
    blk();
    return 0;
}

经过-rewrite-objc转换后变成如下的C++代码:

//描述Block最小单元,主要成员是void *FuncPtr,指向Block代表的匿名函数。
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//该结构体即代表Block,结构体中int val就是自动截取的变量(初始化时用Block中需要访问的值来赋值),struct __block_impl中最主要的就是void *FuncPtr,用来指向Block代表的匿名函数。
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//Block代表的匿名函数,即本文中的“printf("val = %d\n",val);”
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy

 printf("val = %d\n",val);
    }

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 val = 10;
    //Block定义
     void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
     //Block调用
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

从上面可以看出Block转变成了struct __main_block_impl_0,自动截获的val也变成了结构体的成员变量,然后在结构体的构造函数中用需要截获的变量值进行初始化。至此可以清晰地看出Blcok的本质是struct,是Objective-C的对象。

二、Block和函数的区别

  • 静态与动态

Block是Objective-C对象,既然是对象,那么就是运行时的产物,只有运行的时候才能确定;而函数代表的是地址,它是静态的,在编译器就确定了。所以Block与函数最大的区别就是产生的时机不同。

  • 嵌套定义

Block可以嵌套定义,比如:

void main()
{
    void (^externalBlock)(void) = ^(void){
        void(^internalBlock)(void) = ^(void){
            NSLog(@"internalBlock");
        };
        internalBlock();
    };
    externalBlock();
}

但函数却不能嵌套定义,比如你不能编写这样的函数:

void main()
{
    void fun1(void)
    {
        void fun2(void);
        {
            printf("fun2");
        }
        fun2();
    }
}

三、__block修饰符的含义

当我们想要在Block内部修改某个自动局部变量时,必须在自动局部变量前加上__block修饰符。那么大家想过为什么一定要加__block呢,不加__block为什么就不能修改呢?想要回答这写问题可以从本文的第一小节入手。
在第一小节中自动局部变量val经过编译器转换后变成struct __main_block_impl_0中个一个成员int val,然后在struct __main_block_impl_0的构造函数中用截获的自动变量_val的值初始化struct __main_block_impl_0的成员int val,从整个构造过程可以看出val是值传递而不是引用传递,所以在Block中是修改不了Block外部的自动变量的值,这个问题就好比如大家都学过的swap函数,要想能交换两个变量的值,swap函数的形参必须是地址。
那么增加__block修饰符变换后的C++代码是什么样子呢?我们依然用clang 的-rewrite-objc命令,假设Block源码为:

#include"stdio.h"
int main()
{    
    __block int val = 10;
        void (^blk)(void) = ^{
            val = 11;
            printf("val = %d\n",val);
    };
    blk();
    return 0;
}

转换后变成:

//__block修改的自动变量变成了一个struct,然后struct中有一个成员保存自动变量的值
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

//从构造函数可以看出用__block修饰的变量是用引用传递的,故而能修改外部自动变量的值
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref

         (val->__forwarding->val) = 11;
         printf("val = %d\n",(val->__forwarding->val));
    }

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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()
{
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
     void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

从转换后的C++代码可以看出用__block修饰的变量最后变化成了一个struct,然后struct中有一个成员变量val存储着自动变量的值,一个成员__forwarding存储着外部变量的地址,犹如这行代码所说明的

__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};

在构造Block中时传递的是(__Block_byref_val_0 *)&val,也就是说用__block修饰的变量是通过引用传递被Block所捕获的,所以可以在Block内部修改外面的自动局部变量。

PS:想在Block内部修改Block外部的变量,除了用__block修饰符外,也可以把变量定义为静态局部变量、静态全局变量、全局变量