Blocks

参考:Objective-C高级编程 iOS与OS X多线程和内存管理

1.1 Blocks概要

1.1.1 什么是Blocks

  • BlocksC语言的扩充功能,用一句话表示 带有自动变量(局部变量)的匿名函数
  • Blocks中将匿名函数部分称为Block literal,或简称为Block

1.2 Blocks模式

1.2.1 Block语法

不在本文讨论范围内,不做详细说明

1.2.2 Block类型变量

声明Block类型变量的示例如下: int (^blk)(int); 该Block类型变量与一般的C语言变量完全相同,可以作为以下用途

> 自动变量
> 函数参数
> 静态变量
> 静态全局变量
> 全局变量

typedef int (^blk_t)(int);通过typedef可声明blk_t类型变量

1.2.3 截获自动变量值

带有自动变量值在Blocks中表现为截获自动变量值

int main()
{
   
    int val = 10;
    
    const char *fmt = "val = %d\n";
    
    void(^blk)(void) = ^{printf(fmt,val);};
    
    val = 2;
    
    fmt = "These values were changed .val = %d\n";
    
    blk();
    
    return 0;
} 

打印结果:val = 10

Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。所以执行Block语法后,即使改变了Block中使用的自动变量的值也不会影响到Block执行时自动变量的值.

1.2.4 _ _block说明符

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符

1.2.5 截获的自动变量

int main()
{
    id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        id obj = [[NSObject alloc] init];
        
        [array addObject:obj];
    };
    
    return 0;
}

这是没有问题的,而面向截获的array赋值则会产生编译错误。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。

另外,在使用C语言数组时必须小心使用其指针,源代码示例如下:

int main()
{
    const char text[] = "hello";
    
    void(^blk)(void) = ^{printf("%c\n",text[2]);};
    
    return 0;
}

只是使用C语言的字符串字面量数组,而并没有想截获的自动变量赋值,因此看似没有问题,但是实际上产生了编译错误。原因在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时,使用指针就可以解决问题:

int main()
{
    const char *text = "hello";
    
    void(^blk)(void) = ^{printf("%c\n",text[2]);};
    
    blk();
    
    return 0;
}

1.3 Blocks的实现

1.3.1 Block的实质

我们首先通过clang(LLVM编译器)将含有Block语法的源代码变换成C++的源代码(说是C++,其实也仅是使用了Struct结构,其本质是C语言源代码)。
转换语句clang -rewrite-objc 源代码文件名

下面我们转换Block语法

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) {
        //_NSConcreteStackBlock用于初始化_block_impl结构体的isa成员
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main()
{
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

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

    return 0;
}

下面分成几部分逐步理解,首先来看看最初的源代码中Block语法。
^{printf("%c\n",text[2]);};
可以看到,变换后的源代码中也含有相同的表达式

 //参数__cself为指向Block值的变量(类似于Objective-C实例方法中指向对象自身的变量self)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
    printf("Block\n");
}

如变换后的源代码所示,通过Blocks使用的匿名函数实际上被作为简单的C语言函数来处理.另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。

这里可能要进行补充

先来看看构造函数的调用

void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下:

    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;

以下为这部分代码最初的源代码

void(^blk)(void) = ^{printf("Block\n");};

我们来确认一下使用该Block的部分

blk()

这部分可变换为以下源代码

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

去掉转换部分

//static void __main_block_func_0(struct __main_block_impl_0 *__cself) 实际上是使用这个函数的指针调用函数
(*blk -> impl.FuncPtr)(blk);

这就是简单的使用函数指针调用函数。由Block语法转换的__main_block_func_0函数的指针被赋值到成员变量FuncPtr中。另外也说明了,__main_block_func_0函数的参数__cself指向Block值。在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

下面对_NSConcreteStackBlock进行说明

//将Block指针赋给Block的结构体成员变量isa
 impl.isa = &_NSConcreteStackBlock;

其实,所谓Block就是Objective-C对象。
id这一变量类型用于存储Objective-C对象。在Objective-C源代码中,虽然可以向使用Void *类型那样随意的使用id,但此id类型也能够在C语言中声明。在/usr/include/objc/runtime.h中是如下声明的

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
};
///Class为objc_class结构体的指针类型
typedef struct objc_class *Class;


struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// id为objc_object结构体的指针类型
typedef struct objc_object *id;

Objective-C中由类生成对象意味着,像结构体这样生成由该类生成的对象的结构体实例。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

_NSConcreteStackBlock相当于class_t结构体实例

1.3.2 截获自动变量值

将截获自动变量值的源代码通过clang进行转换

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  printf(fmt,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;
    const char *fmt = "val = %d\n";
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 2;
    fmt = "These values were changed. val = %d\n";

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

    return 0;
}

首先我们注意到,Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0结构体中。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
};

下面来看看初始化该结构体的构造函数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) 

构造函数调用结果如下

{
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    fmt = "val = %d\n";
    val = 10;
  }

由此可知,在__main_block_impl_0结构体实例(Block)中,自动变量值被截获。
下面来看一下使用Block的匿名函数的实现

^{printf(fmt,val);}

该源代码可转换为以下函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  printf(fmt,val);
}

总得来说,所谓截获自动变量意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。

1.3.3 _ _block说明符

int main()
{
    __block int val = 10;
    
    void(^blk)(void) = ^{val = 1;};
    
    return 0;
}

clang转换代码后

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

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) = 1;
}

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

    return 0;
}

其中__block int val = 10;转换为结构体实例,即栈上生成的__Block_byref_val_0结构体实例

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

而且这个值出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。该结构体声明如下:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

下面这段给__Block变量赋值的代码^{val = 1;}该源码转换如下:

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) = 1;
}

Block__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例指针。__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val.

下面主要说明两个部分
1.Block超出变量作用域可存在的理由
2.__block变量的结构体成员变量_forwarding存在的理由
有时在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情形下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,就可以正确进行访问。

1.3.4 Block存储域

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

将Block当作Objective-C对象来看时,该Block的类为_NSConcreteStackBlock

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

只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化

向方法或函数的参数中传递Block时需要手动复制Block,但以下方法或函数不需要

 * Cocoa框架的方法名中含有usingBlock等时
 * GCD的API

将Block从栈上复制到堆上是相当消耗CPU的,因此只在此情形下让编程人员手动进行复制。

Block 的副本

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

1.3.5 _ _block变量存储域

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

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

int main()
{
    __block int val = 10;
    
    //这个val使用的是复制到堆上的__block变量
    void(^blk)(void) = [^{++val;} copy];
    
     //为复制前栈上的__block变量用的结构体实例,但__forwarding 指向目标堆上的__block变量用结构体实例的地址
    ++val;
    
    blk();
    
    NSLog(@"%d",val);
    
    return 0;
}

1.3.6 截获对象

除了以下情形外,推荐使用Blockcopy实例方法

  • Block 作为函数返回值返回时
  • 将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时
  • 向方法名中含有usingBlock的cocoa框架方法或GCD的API中传递Block时

你可能感兴趣的:(Blocks)