深入浅出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函数被回调的过程 的对比

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

总结:

  

补充:可能有读者觉得那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 源代码文件名

然后我们打开这个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++的结构体知识,那么读者可以阅读本人的《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的创建和执行。

 3-2、截获局部变量值的block底层实质分析

根据上面简单的介绍,下面就不多累述了,直接截图出逻辑原理的代码图:

 为了方便用"——>"画图,我将block_func和block_impl两个结构体调换位置。大家能理解就行。

   总结一下,如果面试问到读者:请说说block的底层实现原理,读者可以这么回答:

"将block源码反编译成C++源码,可以看到,block底层通过使用C++的带有可以初始化成员变量的构造方法的结构体,来存储OC源码block截获的局部变量,并且这个结构体中还有一个成员函数指针,通过构造函数初始化可以指向另外声明的一个函数,而这个函数就包含了OC源码中block大括号括起来的代码逻辑,当我们执行这个block的时候,block底层C++源码就会取出这个结构体的成员函数指针变量,然后执行成员函数指针所指向的函数,这个函数包含两部分:1、在这个函数中取出了结构体的成员变量,这个成员变量存储了OC源码截获的局部变量,2、执行了所包含的OC源码中block大括号括起来的那部分代码逻辑"。

时间: 2024-09-20 05:56:21

深入浅出Block的方方面面的相关文章

深入浅出Cocoa多线程编程之 block 与 dispatch quene

深入浅出 Cocoa 多线程编程之 block 与 dispatch quene 罗朝辉(http://www.cppblog.com/kesalin CC 许可,转载请注明出处 block 是 Apple 在 GCC 4.2 中扩充的新语法特性,其目的是支持多核并行编程.我们可以将 dispatch_queue 与 block 结合起来使用,方便进行多线程编程. 本文源代码下载:点击下载 1,实验工程准备 在 XCode 4.0 中,我们建立一个 Mac OS X Application 类型

深入浅出Java多线程程序设计

程序|多线程|设计 一:理解多线程 多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立. 线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单. 多个线程的执行是并发的,也就是在逻辑上"同时",而不管是否是物理上的"同时".如果系统只有一个CPU,那么真正的"同时"是

深入浅出多线程(3)-Future异步模式以及在JDK1.5Concurrent包中的实现

接深入浅出多线程(2)在多线程交互的中,经常有一个线程需要得到另个一 线程的计算结果,我们常用的是Future异步模式来加以解决. 什么是Future模式呢?Future 顾名思义,在金融行业叫期权,市场上有看跌 期权和看涨期权,你可以在现在(比如九月份)购买年底(十二月)的石油,假 如你买的是看涨期权,那么如果石油真的涨了,你也可以在十二月份依照九月份 商定的价格购买.扯远了,Future就是你可以拿到未来的结果.对于多线程,如 果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果

深入浅出Java多线程(1)-方法join

对于Java开发人员,多线程应该是必须熟练应用的知识点,特别是开发基于 Java语言的产品.本文将深入浅出的表述Java多线程的知识点,在后续的系列里 将侧重于Java5由Doug Lea教授提供的Concurrent并行包的设计思想以及具体实 现与应用. 如何才能深入浅出呢,我的理解是带着问题,而不是泛泛的看.所以该系列 基本以解决问题为主,当然我也非常希望读者能够提出更好的解决问题的方案以 及提出更多的问题.由于水平有限,如果有什么错误之处,请大家提出,共同讨 论,总之,我希望通过该系列我们

深入浅出Win32多线程程序设计之基本概念

一.深入浅出Win32多线程程序设计之基本概念[转] 引言 从单进程单线程到多进程多线程是操作系统发展的一种必然趋势,当年的DOS系统属于单任务操作系统,最优秀的程序员也只能通过驻留内存的方式实现所谓的"多任务",而如今的Win32操作系统却可以一边听音乐,一边编程,一边打印文档. 理解多线程及其同步.互斥等通信方式是理解现代操作系统的关键一环,当我们精通了Win32多线程程序设计后,理解和学习其它操作系统的多任务控制也非常容易.许多程序员从来没有学习过嵌入式系统领域著名的操作系统Vx

深入浅出讲解ES6的解构_基础知识

什么是解构? 解构与构造数据截然相反. 例如,它不是构造一个新的对象或数组,而是逐个拆分现有的对象或数组,来提取你所需要的数据. ES6使用了一种新模式来匹配你想要提取的数值, 解构赋值就是采用了这种模式. 该模式会映射出你正在解构的数据结构,只有那些与该模式相匹配的数据,才会被提取出来. 被解构的数据项位于赋值运算符 = 的右侧,可以是任何数组和对象的组合,允许随意嵌套.用于给这些数据赋值的变量个数不限. 数组解构 数组解构 使用一个数组作为一个数据项,你可以根据 数组模式 (用于从数组中匹配

深入理解Ruby中的block概念_ruby专题

Ruby 里的 block一般翻译成代码块,block 刚开始看上去有点奇怪,因为很多语言里面没有这样的东西.事实上它还不错.First-class function and Higher-order function First-class function 和 Higher-order function 是函数式编程语言里面的概念,听起来好像很高端的样子,其实很很简单的. First-class functions 是指在某些语言里,函数是一等公民,可以把函数当做参数传递, 可以返回一个函数

深入讲解Ruby中Block代码快的用法_ruby专题

Block 定义 some_array.each { |value| puts value + 3 } sum = 0 other_array.each do |value| sum += value puts value / sum end     A block is somewhat like the body of an anonymous method     Block can take parameters     Block 只有被 method 调用时才会起作用,如果 meth

深入理解Ruby中的代码块block特性_ruby专题

block是什么? 在Ruby中,block并不罕见.官方对block的定义是"一段被包裹着的代码".当然,我觉得这样的解释不会让你变的更明白. 对block的一种更简单的描述是"一个block就是一段存储在一个变量中的代码,它和其他的对象一样,可以被随时的运行" 然后,咱们通过看一些代码,之后再把这些代码重构成Ruby中的block形式.通过代码来实际的感受,更加直观. 比如,对两个数做加法? puts 5 + 6 # => 11 嗯,这样写是可以的.但是,