本文介绍: Blocks简单概括为:带有自动变量局部变量)的匿名函数使用Blocks时,可以声明C++类和Objective-C类,也没有使用静态变量静态全局变量全局变量时的问题,仅用编写C语言函数源代码即可使用带有自动变量值的匿名函数。对于“带有自动变量值的匿名函数”这一概念并不仅指Blocks。它还存在于许多其他程序语言中。在计算机科学中,此概念也称为闭包(Closure)、lambda计算(λ计算)等。

Blocks概要

什么是Blocks

Blocks简单概括为:

带有自动变量局部变量)的匿名函数

在使用Blocks时,可以不声明C++类和Objective-C类,也没有使用静态变量静态全局变量全局变量时的问题,仅用编写C语言函数的源代码即可使用带有自动变量值的匿名函数。

对于“带有自动变量值的匿名函数”这一概念并不仅指Blocks。它还存在于许多其他程序语言中。在计算机科学中,此概念也称为闭包(Closure)、lambda计算(λ计算)等。

请添加图片描述

Block语法

完整的Block语法与一般的C语言函数定义相比,仅有两点不同

  1. 没有函数名
  2. 带有“^”

^:
由于OS X、iOS应用程序源代码中将大量使用Block,所以插入该记号便于查找请添加图片描述

例如写出下面形式的Block语法

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

省略返回值类型时:

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

省略返回值类型时,如果表达式中与return语句就使用该返回值类型;如果没有return语句就使用void类型;当表达式中含有多个return语句时,所有return的返回值类型必须相同

如果不使用参数参数列表也可省略:

^void (void) {printf("Blocksn");}

上述代码可以省略为:

^{printf("Blocksn");}

Block类型变量

声明block类型变量的实例如下

int (^blk) (int);

使用block语法将Block赋值给Block类型变量:

int (^blk) (int) = ^int (int count) {return count + 1;};

由”^”开始的 Block 语法生成的 Block 被赋值给变量 blk 中。因为与通常的变量相同,所以当然也可以由Block类型变量向 Block 类型变量赋值:

int(^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

在函数参数中使用 Block类型变量可以向函数传递 Block。

void func(int (^blk)(int)) {//...}

在函数返回值指定 Block 类型,可以将 Block 作为函数的返回值返回

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

由此可知,在函数参数返回值中使用 Block 类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用 typedef解决问题

typedef int (^blk t)(int);

如上所示通过使用 typedef可声明”blk t”类型变量。我们试着在以上例子中的函数参数和函数返回部分里使用一下。

/*原来的记述方式
void func(int (^blk)(int) )
*/

void func (blk_t blk) {

/* 原来的记述方式
int(^func ()(int))
*/

blk_t func() {

通过 Block类型变量调用Block与C语言通常的函数调用没有区别。在函数参数中使用Block类型变量并在函数中执行 Block 的例子如下

int func(blk_t blk,int rate) {
	return blk (rate);
}

当然,在 Objective-C的方法中也可使用。

-(int) methodUsingBlock:(blk_t)blk rate:(int)rate {
	return blk (rate);
}

截获自动变量值

int main() {
	int dmy = 256;
	int val = 10;
	const char *fmt = "val = 号dn";
	void (^b1k)(void) = ^{printf(fmt,val);};
	val = 2;
	fmt = "These values were changed. val = %dn";
	blk();
	return 0;
}

结果

val= 10

执行结果并不是改写后的值”These values were changed. val=2″,而是执行 Block 语法时的自动变量的瞬间值。该Block语法在执行时,字符串指针val=%dn”被赋值到自动变量 fmt中,int 值10被赋值到自动变量 val中,因此这些值被保存(即被截获),从而在执行块时使用。
这就是自动变量值的截获。

__block说明

实际上,自动变量值截获只能保存执行 Block 语法瞬间的值。保存后就不能改写该值。下面我们尝试改写截获的自动变量值,看看会出现什么结果。下面的源代码中,Block 语法之前声明的自动变量 val 的值被赋予1。

int val=0
void ("blk)(void) = ^{val = 1;};
blk();
printf("val = %dn",val);

以上为在 Block 语法外声明的给自动变量赋值的源代码。该源代码会产生编译错误

error: variable is not assignable (missing __block type specifier)
void (^blk)(void)= ^{val = 1;};

若想在 Block 语法的表达式中将值赋给在 Block 语法外声明的自动变量,需要在该自动变量上附加 block 说明符。该源代码中,如果给自动变量声明 int val附加 block 说明符,就能实现在Block 内赋值。

_block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = dn",val);

执行结果

val =1

那么截获 Objective-C 对象调用变更该对象的方法也会产生编译错误吗?

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

这样是不会出错的,但是如果是向截获的变量 array 赋值则会产生编译错误。该源代码中截获的变量值为NSMutableArray 类的对象。如果用C语言来描述,即是截获NSMutableArray 类对象用的结构实例指针。虽然赋值给截获的自动变量 array 的操作会产生编译错误,但使用截获的值却不会有任何问题

下面源代码向截获的自动变量进行赋值,因此会产生编译错误。

id array =[[NSMutableArray alloc] init];
void(^b1k)(void) = ^(
array =[[NSMutableArray alloc] init];
};
error: variable is not assignable(missingblock type specifier)
array = [[NSMutableArray alloc] init];

这种情况下,需要给截获的自动变量附加_block 说明符。

__block id array =[[NSMutableArray alloc] init];
void (^blk)(void) = ^{
	array = [[NSMutableArray alloc] init];
};

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

const char text[] = "hello";
void (^blk)(void) = ^{
	printf("%cn", text[2]);
};

只是使用C语言的字符串字面数组,而并没有向截获的自动变量赋值,因此看似没有问题。但实际上会产生以下编译错误∶

error; cannot refer to declaration with an array type inside block
printf("cAn",text[2]);note: declared here
const char text [] ="hello";

这是因为在现在的 Blocks 中,截获自动变量的方法并没有实现对C语言数组的截获。这时,使用指针可以解决问题

const char *text = "hello";
void (^blk)(void)= ^{
	printf("%cn",text [2]);
}

Blocks的实现

Block的实质

Block是”带有自动变量值的匿名函数”,但 Block究竟是什么呢?本节通过Block 的实现一步大家加深理解

前几节讲的 Block 语法看上去好像很特别,但它实际上是作为极普通的C语言源代码处理的。通过支持 Block 的编译器,含有Block 语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C 语言源代码被编译
这不过是概念上的问题,在实际编译时无法转换成我们能够理解的源代码,但 clang(LLVM 编译器具有转换我们可读源代码的功能。通过”-rewriteobjc选项就能将含有Block 语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。

clang -rewrite-objc 源代码文件名

下面,我们转换 Block 语法。

int main() {
	void (^blk)(void) = ^{printf("Blockn");};
	blk();
	return 0;
}

此源代码的 Block 语法最为简单,它省略了返回值类型以及参数列表。该源代码通过 clang 可变换为以下形式∶

//经过clang转换后的C++代码
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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself {
	printf("Blockn");
}

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, const char * argv[]) {
	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;
}

8 行的源代码竟然增加到了43行。但是如果仔细观察就能发现,这段源代码虽长却不那么复杂。下面,我们将源代码分成几个部分逐步理解。首先来看最初的源代码中的 Block 语法。

^{printf("Blockn")};

可以看到,变换后的源代码中也含有相同的表达式

static void__main_block_func_0(struct_main_block_impl_0 *__cself){
	printf("Blockn");
}

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

__block_impl结构体:

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

结构体的名称:impl即implementation的缩写,换句话说这一部分是block的实现部分结构体。

void *isa:声明一个不确定类型的指针用于保存Block结构实例

int Flags:标识符

int Reserved:今后版本升级所需的区域大小

void *FuncPtr:函数指针指向实际执行的函数,也就是block中花括号里面的代码内容

static struct __main_block_desc_0结构体:

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

第一个成员变量指的是今后版本升级所需区域大小(一般填0)。

第二个成员变量是Block的大小

在定义完最后一个结构体实例变量,变量名就是__main_block_desc_0_DATA。

最后进行一个赋值操作其中reserved为0,Block_sizesizeof(struct __main_block_impl_0)。

struct __main_block_impl_0结构体:

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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

第一个成员变量是impl,也就是上面点1中的结构体的变量。
第二个成员变量是Desc指针,就是上面点2中的结构体的指针。
剩下的代码就是:初始化上面的两个成员变量

static void __main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself {
	printf("Blockn");
}

这一部分就是Blcok执行的实际代码块。也是点3中fp指针指向的函数。括号中的参数__cself是相当于OC语言版的self代表的是Block本身。

main函数

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

该代码看似复杂去掉转换的部分,第一行具体如下

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

该代码首先将栈上生成的__main_block_impl_0结构体的实例赋值给temp,然后把该实例的指针赋值给blk变量

第二行如下

(*blk->impl.FuncPtr)(blk);

相当于源代码中的blk()
以上就是Block的实质,Block即为Objective-C对象

截获自动变量

int main(int argc, const char * argv[]) {
	int dmy = 256;
    int val = 10;
    const char  *fmt = "val = %dn";
    void (^blk)(void) = ^{
    	printf(fmt, val);
    };
    blk();
    return 0;
}

和上面一样,将截获自动变量值的源代码通过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;
  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 = %dn";
    val = 10;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself {
  const char *fmt = __cself->fmt;
  int val = __cself->val; 
  
	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 argc, const char * argv[]) {
	int dmy = 256;
    int val = 10;
    const char *fmt = "val = %dn";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    return 0;
}

和上面相比的不同之处在于下面,注意__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;
  }
};

里面出现了const char *fmt; int val;
也就是block中使用的自动变量被当作成员变量添加到了该结构体中。

该结构体的实例构造函数也发生了相应的改变:

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

初始化时自动变量fmt和val进行了赋值操作:

	impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    fmt = "val = %dn";
    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;
  int val = __cself->val; 
  
	printf(fmt, val);
}

在转换后的代码中,截获到__main_block_impl_0结构体实例成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义,所以之后即使改变自动变量的值也不会对Block语法中的内容有所变化。

__block说明

我们知道在block中不能修改已经捕获的自动变量在block外的值,但是我们在日常开发中时常有这个需求,此时使用__block说明修饰自动变量就可以实现在block中修改自动变量在block外的值
__block的作用见下方代码:

int main(int argc, const char * argv[]) {
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    	printf("val = %dn", val);
    };
    blk();
    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; 
  (val->__forwarding->val) = 1;
  printf("val = %dn", (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, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src {
	_Block_object_dispose((void*)src->val, 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[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0
}

__Block_byref_val_0结构体

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

重点在于__Block_byref_val_0 *__forwarding,这个相当于指向该结构体本身实例的一个指针

__main_block_impl_0结构体

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

这部分值得注意的是,对于我们的__Block_byref_val_0结构体,我们同样是用一个指针去保存,这么做的原因是通过__block修饰的变量可能会被不止一个block使用,使用指针可以保证其可以被多个block调用

static void __main_block_func_0函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; 
  (val->__forwarding->val) = 1;
  printf("val = %dn", (val->__forwarding->val));
}

这里需要注意的是,对val赋值的时候需要通过forwarding指针,forwarding指针指向内存中的结构体实例然后访问其中的成员变量。

主函数:

int main(int argc, const char * argv[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0;
}

看一下__block修饰的变量的赋值:

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

看一下__block修饰的变量的赋值:

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

这个__block变量val变为了__Block_byref_val_0结构体变量。然后在下方通过调用 static void __main_block_func_0函数(通过__Block_byref_val_0结构体成员变量__forwarding访问成员变量val),将10赋给val。

原文地址:https://blog.csdn.net/zdsey/article/details/129308282

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_32362.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注