深入浅出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大括号括起来的那部分代码逻辑"。

相关文章
|
9月前
|
传感器 安全 算法
【C语言】C语言可以做什么?
C语言因其高效、灵活和低级控制能力,被广泛应用于各个领域,从基础设施和科学计算到金融、交通和机器人技术。它在许多关键应用中展示了其不可替代的价值和广泛的适用性。
435 2
|
关系型数据库 MySQL 数据安全/隐私保护
windows mysql8 安装后 提示密码不对,修改下密码认证方式就可以了
windows mysql8 安装后 提示密码不对,修改下密码认证方式就可以了
2148 3
|
7月前
|
数据采集 监控 安全
电商数据接口实战:全量获取店铺商品的技术方案与进阶策略
该文档介绍了通过官方API获取店铺全量商品数据的技术实现与应用场景。主要涵盖四大方面:业务场景(如店铺运营监控、竞品分析等)、技术实现流程(包括环境准备、接口调用和分页策略)、数据结构解析与治理(如响应结构、数据处理建议),以及企业级解决方案设计(架构设计、性能优化、数据更新策略)。同时,强调了合规与安全实践,并提供了典型问题的解决方案。适用于电商中台项目,支持日均亿级商品数据处理。
|
Kubernetes 监控 API
Rancher 系列文章 -Rancher v2.6 使用脚本实现导入集群
Rancher 系列文章 -Rancher v2.6 使用脚本实现导入集群
|
机器学习/深度学习 自然语言处理 算法
【论文精读】TNNLS 2022 - 基于深度学习的事件抽取研究综述
事件抽取是从海量文本数据中快速获取事件信息的一项重要研究任务。随着深度学习的快速发展,基于深度学习技术的事件抽取已成为研究热点。文献中提出了许多方法、数据集和评估指标,这增加全面更新调研的需求。
843 0
|
传感器 自动驾驶 安全
无人驾驶汽车对人民的出行方式和生活方式产生了深远的影响
无人驾驶汽车对人民的出行方式和生活方式产生了深远的影响
无人驾驶汽车对人民的出行方式和生活方式产生了深远的影响
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp微信小程序的二手物品交易平台的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的二手物品交易平台的详细设计和实现
152 0
|
监控 安全 网络安全
什么是HW,企业如何进行HW保障?
护网行动是一种积极、有效的网络安全防护措施,它通过实战化的演练来提升网络安全防护能力,确保网络系统的安全和稳定。
1843 0
|
弹性计算 运维 监控
阿里云轻量应用服务器和ECS有什么区别?如何选择?十大区别对比
阿里云服务器分为轻量应用服务器和云服务器ECS,轻量是在云服务器ECS基础上推出的轻量级云服务器,轻量应用服务器是一款可快速搭建且易于管理的轻量级云服务器,提供基于单台服务器的应用部署、安全管理、可视化运维监控等服务,使用门槛低。轻量应用服务器结合WordPress等应用镜像,可以快速搭建所需Web环境
1518 0
阿里云轻量应用服务器和ECS有什么区别?如何选择?十大区别对比
|
安全 Linux 网络安全
2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-A-CTF攻击与防御
模块C和D是CTF夺旗比赛,分别聚焦攻击和防御。攻击者需利用各种漏洞(如命令注入、文件上传、远程代码执行等)攻陷靶机,获取权限,但禁止攻击裁判服务器。防御者则需检测并加固堡垒服务器,确保服务可用性,发现并修补同样类型的漏洞。比赛分数依据靶机的flag值和加固报告,强调不提供补时。操作环境涉及Windows和Linux,选手需使用谷歌浏览器登录。注意提交清晰的截图和PDF报告至U盘。
320 0