你的位置:首页 > 操作系统

[操作系统]深入浅出Block的方方面面(持续更新)


内容大纲:

1、Blocks概要

2、Blocks模式

3、Block实质(面试常问重点)

 

1、Blocks概要

什么是Blocks:Blocks是C语言的扩充的功能,可以用一句话来表示Blocks的扩充功能:带有局部变量(有的资料局部变量也叫自动变量)的匿名函数。这个函数叫block。 (注意Blocks是一种功能,block是一种函数)

1-1、关于"匿名函数"

匿名函数:不带有名称的函数就是匿名函数。(但是C语言的标准不允许存在这样的函数。)

例如下面的源代码:在赋值给函数指针时,若不使用赋值的函数的名称,就无法取得该函数的地址:

  

然而通过Block,源代码中就能够使用匿名函数,即不带名称的函数。

1-2、关于"带有局部变量(有的资料讲局部变量也叫自动变量)"

这里为了能够让你们更好的理解这个"带有局部变量(有的资料局部变量也叫自动变量)",我需要拿普通的函数被回调的过程Block函数被回调的过程进行对比,这样能够让读者更好的理解这个block的"带有局部变量(有的资料局部变量也叫自动变量)"的意义,以及这个block的特点。

(瞎扯两句:通过对比出与众不同的地方是可以成为特点的😉)

普通函数被回调的过程 和 block函数被回调的过程 的对比

http://i11.tietuku.com/5fddc7dcd28cff30.png

为了能让读者进一步体会block的特性,本人分别在上面两个源码的函数被调用执行之前相应的地方添加了大括号设置局部变量的作用域,请看下面代码理解

http://i11.tietuku.com/1d8a9e54f221f8e1.png

总结:

  

补充:可能有读者觉得那block肯定能获取全局变量,可是全局变量普通函数也能获取啊,所以没必要在这里扯全局变量。(这句补充的话读者要是还不懂,那你就自行脑补吧。👻)

另 外介绍:"带有局部变量的匿名函数"这一概念并不仅指Blocks,它还存在于其他许多编程语言中,在计算机中,此概念也称为闭包(Closure)、 lambda计算(λ计算,lambda calculus)等,Objective-C的Block在其他程序语言中的名称如下表格:

            

 

2、Blocks模式

2-1、Block 语法

Block语法格式:^ 返回值类型 (参数列表) {表达式}

  

省略形式的语法(只有两种):

    • 省略了返回值类型:^ (参数列表) {表达式}


    • 省略了返回值类型和参数列表:^ {表达式}


2-2、Block 类型变量

先简单讲讲基本数据类型的类型变量,int a = 2这个a就是int类型的变量,这个变量a存储着具体的值:2。

再讲讲函数指针类型变量:

  

这个funcptr就是函数指针类型的变量。这个变量指向(指针类型所以说是指向)func函数的地址。

那么同样的,在Block语法下,可将前面讲的"Block语法"赋值给声明为Block类型的变量中。

声明Block类型变量的格式:

返回值 (^变量名)(参数列表)

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

  

关于Block类型变量声明部分的快熟记忆的方法:首先你肯定知道普通函数声明函数头部分:int func(),那么block的声明就是int (^func)(),其实就是在函数名的地方加了(^函数名),对比一下,是不是很好记了。

2-3 Block类型变量的使用

block类型变量,作为变量,它可以用在函数参数和返回值,但是这样的话,记述方式极为复杂。这时,我们可以像使用函数指针类型那样,使用typedef来解决这个问题:

  

2-4、截获局部变量值

关于截获局部变量值,其实在前面"1、Block概要"中其实已经介绍了,通过在适当的位置使用大括号,能够验证出Block会将局部变量拷贝一份为自己所拥有。

下面,通过另一种情况来验证,其实也很简单的,就是通过对外部的局部变量重新赋值。让我们再来看看这个"带有局部变量的匿名函数":

  

这个就是局部变量值的截获,截获之后被这个block所持有,因此叫做"带有局部变量的匿名函数"。

2-5、__block说明符

实际上,虽然block可以截获并拿到这个局部变量的值,但是却不能在block内部直接更改它,下面的代码会产生编译错误:

  

解决方法就是在这个m变量前面使用__block说明符

  

使用附有block说明符的自动变量可以在Block中赋值,该变量称为blcok变量。

2-6、截获的局部变量相关的问题

3、Blocks的实现(面试常问重点)

3-1、Block的实质

Block是"带有局部变量的匿名函数",但是Block究竟是什么呢?本节将通过Block的实现进一步帮大家加深理解。

要想理解Block的实质,需要通过下面的终端命令将Block反编译成底层C++的源代码,虽然说是C++代码,其实也是仅仅使用了C++的struct结构,其本质还是C语言的源代码。

clang -rewrite-objc 源代码文件名

http://chuantu.biz/t2/29/1456995476x3074288733.png

 

然后我们打开这个main.cpp文件,你会发现内容好多啊,多的你不要不要的:

没关系,很多代码都不是重点,重点的是和Block相关的代码,我们在main.cpp中可以先找到如下图的main函数,然后利用XCode颜色高亮插件DDHighlight,选择相关的关键字,同样关键字都会呈现出颜色,因为他们之间肯定存在调用和被调用的关系这样main函数中block低层实现的源码相关的东西,都可以找出,然后删除其他上百行无关的东西,也就剩下下面这30多行:

 开始分析C++源码,如图是main函数中的block声明部分和block执行部分的代码和对应C++的源码:

我们先分析分析block声明的那段代码,block执行的部分先放一边,然后如图我做了进一步的处理,读者可以自己看图,我将声明结构体__block_impl内部的变量抽离出来,

替换调用调用了结构体类型__block_impl声明impl的部分,以及处理了使用结构体变量impl的部分,不难,本质还是没变的:

  

按照上面意思我去掉不必要的注释,并且为了大家能够看得更清楚,我将其中长长的名字替换成简单的一目了然的名字,

比如__main_block_impl_0我全部替换成block_impl:

 

好,接下来就不得不多提一个C++的基础了,还好本人学过C++的基础部分敲过代码,上面一个代码的图中黄色框起来的部分是C++的结构体,在结构体blcok_impl中,多了一个看起来像C函数的函数,好像和我们习惯用的C语言结构体内部不带函数有区别,对于有一定Java基础或者是Swift基础或者是C++基础就会很熟悉这部分基础,如果不熟悉C++的结构体知识,那么读者可以阅读本人的《》。

也就是说,黄色框框的部分,结构体内部使用了构造函数或者是叫构造方法,我们通过初始化构造方法就能创建这个结构体的实例对象。

再看绿色框起来的代码,我把它拷贝过来:block_impl((void *)block_func, &block_desc_DATA),可以看得出,调用了block_impl这个结构体的构造方法,创建了这个block_impl这个结构体的实例对象,只不过,传入了两个参数:1、(void *)block_func 2、&block_desc_DATA ,而原构造函数需要三个参数:1、void *fp 2、struct block_desc *desc 3、int flags=0,很显然第三个参数已经被默认赋值为0,所以可以不需要传第三个参数。

 关于block_desc_DATA这个我就不详说了,知道这么个东西就好,直接说说block_func,很明显,这个block的底层实现,就是创建了一个函数指针指向了一个函数,这个函数内部的代码逻辑就是我们Objective-C使用block所包含的代码逻辑。

下面,我们加进去之前为了避免干扰而删除的执行block部分的代码:

 去掉多余的部分,我们可以看到:

本人虽然会一点C++基础,但是对于这黄色框起来的代码的写法本人也是第一次见过,说以不懂其基础细节有大神懂得话,可以指教指教,

但是不管不懂还是不懂,我们都可以大概的看的出来这黄色框起来的代码的意思就是

  执行指针变量blk指向的函数FuncPtr,而回到前面 FuncPtr = void block_func(struct block_impl *__cself) { printf("Block\n"); }

就这样,完成了Objective-C的block的创建和执行。

 

 

 

//C -> C++ -> OC

//&_mainblockimpl0就是那个结构体本身(地址传值其实传递的就是变量本身)

 

转载需要注明出处:http://www.cnblogs.com/goodboy-heyang/p/block.html,尊重劳动成果。