1、Block 本质上是一个struct结构体,在这个结构体中,最重要的成员是一个函数(当然除函数外还有其他重要的成员)。
2、在开始解析Block之前,首先来回顾一下Block的格式。Block相关的格式有2个:
(1)、Block对象的格式;
(2)、Block变量的格式;
3、对于Block对象,它有几种常见的格式:
(1)、一个完整的Block对象的格式如下:
比如这个Block对象:
可以发现,完整的Block对象和函数的定义非常相似,比如这个函数:
两者之间的区别仅仅是:Block对象多了一个“^”符号,并且不需要像函数一样指定函数名;
(2)、在完整的格式中有一些项目是可以省略的,比如“返回值类型”。不管Block对象是否有返回值,这个项目都是可以省略的,省略之后,只要表达式中有return语句就会自动使用该返回值的类型,如果没有return语句就会使用void类型。
在这种情况下,Block对象的格式就变成了:
所以上文(1)中的Block对象可以写成这样:
(3)、除此之外,如果Block对象不需要参数的话,那么“参数列表”这个项目也是可以省略的,这时候Block对象的格式就变成了:
比如这样的一个Block对象:
4、定义了Block对象之后,还要定义Block变量来持有它,才能更方便地使用Block对象。回顾一下3(1)的函数:
对于这种函数,可以声明这样一个函数指针类型变量来使用它:
这时使用*funcPtr的就相当于在使用func函数了。
同样的,对于3(1)中的Block对象,也可以声明这么一个Block变量:
这时使用block()的时候就相当于在使用这个Block对象了。
对比函数指针和Block变量的声明,可以发现两者也是非常相似的,区别仅仅是将*号换成^号。
5、如果要将Block变量当做参数来传递的话,每次使用都完整地定义它显然是很麻烦的事。比如想要在一个函数里使用Block变量类型的参数,完整的定义方式应该是这样的:
这显然是很繁琐的,这时候就可以通过typedef来简化变量:
那么这个函数就可以定义成:
这时就可以非常方便地使用Block变量了。
6、回顾完Block 相关的格式,接下来开始来讨论Block的实现。
在前文提到过,Block最重要的组成部分是一个函数,这也就意味着,大部分Block能实现的功能,其实使用函数也可以实现的。接下来就先来试一试要如何在不使用Block的情况下实现Block的功能。
思考这么一个问题:假设有两个按钮button1和button2,要求实现以下功能:
(1)、按钮有编号;
(2)、编号可修改;
(3)、点击按钮输出自己的编号。
7、首先简单的方法可以通过使用两个函数来实现:
两个函数分别对应两个按钮的点击,调用函数时把按钮的编号作为参数传进去,便能打印出对应编号。
看起来似乎能满足功能了,但是这种方法的问题也是很明显的:按钮并不持有自己的编号,需要调用函数的时候才将编号传进去。
这种方法显然还不够完善。
8、根据面向对象的思考方式,通过将按钮抽象成类并把编号定义成按钮类的实例变量是可以实现这个功能的。但是这里只想要使用普通C语言来实现,那么可以这么处理,把每个按钮的编号定义成全局变量来进行保存,修改的代码如下:
这样按钮就“持有”了编号,实现了问题的3个要求。
9、回过头来看一看问题,如果要使用Block来实现这3个要求的话,代码会是这样的:
这段代码所实现的功能,和8的代码所实现的功能其实就是一样的了。
10、事实上,这两段代码几乎就是等价的了,8的代码几乎就是9的代码转换成普通C语言后的样子(注意“几乎”这个字眼,实际上5的代码转换成普通C语言后与4的代码还是有不少差别的)。
而对于问题中所提的3个要求,其实就是Block的3个主要功能点:
(1)、能截获变量;
(2)、能截获可变的变量;
(3)、回调。