【Objective-C】浅析Block及其捕获机制
目录
- Block的基本使用
- Block的声明
- Block的实现
- Block的调用
- Block作为形参使用
- Block作为属性使用
- 给Block起别名
- Block的copy
- Block的捕获机制
- auto类型的局部变量
- __block浅析
- static类型的局部变量
- 全局变量
- 其他问题
Block的基本使用
什么是Block?
Block (块),封装了函数调用以及调用环境的 OC 对象,Objective-C闭包(可以在内部访问外部的值),相当于C语言的函数指针,把一个函数写在一个函数内部,而OC并没有函数(方法)嵌套这一语法
Block的声明
void(^blockName)();
int(^blockName2)(int a, int b, int c);
格式: 返回值 (^block名称)(形参列表)
^
代表块的符号
Block的实现
- 无参数无返回值
void(^blockName)(void) = ^{};
- 有参数无返回值
void(^blockName)(int a, int b) = ^(int a, int b){};
- 无参数有返回值
int(^blockName)(void) = ^int{return 3;
};
实现部分的返回值可以省略,像这样:
int(^blockName)(void) = ^{return 3;
};
- 有参数有返回值
int(^blockName)(int a, int b) = ^int(int a, int b){return 3 + a * b;
};
实现部分的返回值int
同样可以省略
Block的调用
//无参数无返回值
blockName();//有参数有返回值
int result = blockName(7, 12);
现在已声明的blockName
代表一个块,那么调用这个块既可以通过blockName(7, 12);
,也可以这样:
int result = ^(int a, int b) {return 3 + a + b;
}(7, 12);
Block作为形参使用
Block作为形式参数在方法中的声明与上述格式略有不同(块的名称在外面)
现在Jaxon
类和Jacky
类中分别实现以下方法:
Jaxon.h
- (void)askJackyForHelp: (void(^)(int num))blockName isOK: (void(^)(BOOL boolValue))completion;
Jaxon.m
- (void)askJackyForHelp:(void (^)(int))blockName isOK:(void (^)(BOOL))completion {blockName(3);//传入completion块的参数非1即0completion(arc4random() % 2);
}
Jacky.m
- (void)helpDoWith: (int)num {NSLog(@"帮忙做事%d次", num);
}
接下来在main
函数中调用:
Jaxon* jaxon = [[Jaxon alloc] init];
[jaxon askJackyForHelp:^(int num) {Jacky* jacky = [[Jacky alloc] init];[jacky helpDoWith: num];} isOK:^(BOOL boolValue) {//成功和失败的概率各占一半if (boolValue) {NSLog(@"帮忙成功");} else {NSLog(@"帮忙失败");}}];
运行结果:
这样是不是可以起到代理的作用,Jaxon
委托Jacky
帮忙做事,Jaxon实现不了的委托Jacky实现,因此Block块也可以用于界面传值或其他需要使用代理模式的程序设计中
Block作为属性使用
给Block起别名
文章开头也提到了块其实也是一种对象,可以将ta理解为一种数据类型
那么也可以用typedef
关键字给Block起别名,看以下示例:
typedef void(^Help)(int num);
typedef void(^Finish)(BOOL boolValue);
上面的方法也就可以这样声明:
- (void)askJackyForHelp:(Help)blockName isOK:(Finish)completion;
块的属性关键字一般需要是是copy
:
@interface Jaxon : NSObject//无别名
@property (nonatomic, copy)void(^helpBlock)(int num);
//有别名
//@property (nonatomic, copy)Help helpBlock;
- (void)askMyselfDo;@end@implementation Jaxon- (void)askMyselfDo {self.helpBlock(5);
}@end
main函数:
Jaxon* jaxon = [[Jaxon alloc] init];
jaxon.helpBlock = ^(int num) {NSLog(@"我自己做%@次", @(num));
};
[jaxon askMyselfDo];
运行结果:
Block的copy
关于copy
关键字,编者也简单了解一下,底层原理以后再加以详细的剖析:
在
ARC
环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下几种情况: 手动调用 block 的
copy`方法时;
- block 作为函数返回值时(Masonry 框架中用很多);
- 将 block 赋值给
__strong
指针时;- block 作为 Cocoa API 中方法名含有
usingBlock
的方法参数时;- block 作为 GCD API 的方法参数时。
block 作为属性的写法:
ARC
下写strong
或者copy
都会对 block 进行强引用,都会自动将 block 从栈 copy 到堆上;
建议都写成copy
,这样 MRC 和 ARC 下一致。
Block刚创建时存放在栈区,使用时copy
到堆区
Block的捕获机制
为保证Block内部能正常访问到外部的变量,Block有一种变量捕获机制
auto类型的局部变量
auto
变量:正常定义出来的变量默认都是auto类型,只是省略了
auto int age = 20;
auto类型的局部变量会被捕获
到block块内部,访问方式为值传递
int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {NSLog(@"%d %p", age, &age);
};
age = 20;
//可以打印出来,说明block块是可以访问到外部信息的
blockName();
NSLog(@"%d %p", age, &age);
根据运行结果可以得出以下两点:
- 当
auto
类型的局部变量被捕获到block块内部时,block内部会自动生成一个相同的成员变量,用来存储这个变量的值,因此打印的block外部的age
地址与内部age
地址不一样 - 由于
值传递
,修改外部age
变量的值,不会影响到block内部的变量
__block浅析
Block内部只能调用外部变量,不能修改:
Block 默认情况下是使用被捕获的外部变量的只读拷贝,因此在 Block 内部无法直接修改外部变量的值
解决办法如下:
- 变量用
static
修饰(原因:捕获static类型的局部变量是指针传递
,可以访问到该变量的内存地址) - 全局变量
__block
(我们只希望临时用一下这个变量临时改一下而已,而改为 static 变量和全局变量会一直在内存中)
当变量被__block
修饰时,block可以修改外部全局变量:
__block int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {age = 30;NSLog(@"%d %p", age, &age);
};
blockName();
NSLog(@"%d %p", age, &age);
static类型的局部变量
static
类型的局部变量会被捕获
到block内部,访问方式指针传递
static int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {NSLog(@"%d %p", age, &age);
};
age = 20;
blockName();
NSLog(@"%d %p", age, &age);
- 当
static
类型的局部变量被捕获到block内部时,block块内部会生成一个相同类型的指针,指向捕获到内部的age
变量的地址 - 由于
指针传递
,修改外部的age
变量的值,会影响到block
内部的age
变量
全局变量
全局变量不会被捕获
到block内部,访问方式为直接访问
int _age = 10;
static int _height = 175;int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"%d %p", _age, &_age);void(^blockName)(void) = ^ {_age = 30;NSLog(@"%d %p", _age, &_age);};blockName();_age = 20;NSLog(@"%d %p", _age, &_age);}return 0;
}
其他问题
对于对象
类型的局部变量,block会连同ta的所有权修饰符
一起捕获
为什么局部变量需要捕获,全局变量不用捕获呢?
- 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
- 局部变量,外部不能直接访问,所以需要捕获;
- auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;
- static 变量会一直保存在内存中, 所以捕获其地址即可
相关文章:

【Objective-C】浅析Block及其捕获机制
目录 Block的基本使用Block的声明Block的实现Block的调用 Block作为形参使用Block作为属性使用给Block起别名Block的copy Block的捕获机制auto类型的局部变量__block浅析static类型的局部变量全局变量 其他问题 Block的基本使用 什么是Block? Block (块…...

GitHub和Gitee的区别以及具体使用
文章目录 GitHub和GiteeGitHub和Gitee区别GitHub的使用Gitee的使用 GitHub和Gitee GitHub和Gitee区别 速度不同:GitHub位于美国,而Gitee位于中国。这意味着在中国使用Gitee可能会有更快的访问速度和更好的稳定性。如果我们希望体验Git飞一般的速度&…...

使用Redis实现分布式锁解决商品超卖问题(接上篇文章)
1. RedLock(红锁)简介 RedLock是一种用于分布式系统的锁定算法,旨在提供分布式锁的高可用性和分布式容错性。它是由Redis的创建者Salvatore Sanfilippo提出的,用于克服Redis单实例的单点故障问题。RedLock的目标是确保在多个Redis…...

2001-2022年全国290+个地级市高铁开通数据
2001-2022年全国290个地级市高铁开通数据 1、时间:2001-2022年 2、范围:298地级市(293地级市数(其中莱芜市2019年撤市设区)4直辖市数 ) 3、来源:国家铁路局、铁路客货运输专刊及相关统计 国…...

【Java 进阶篇】深入浅出:Bootstrap 轮播图
在现代网页设计中,轮播图是一个常见的元素。它们可以用于展示图片、广告、新闻、产品或任何您希望吸引用户注意力的内容。要实现一个轮播图,您通常需要一些复杂的HTML、CSS和JavaScript代码,这对于初学者来说可能会感到困难。但幸运的是&…...
75. 颜色分类
75. 颜色分类 双指针——快慢指针 class Solution {public void sortColors(int[] nums) {int n nums.length;int p0 0;for(int i 0; i < n; i){if(nums[i] 0){swap(nums, p0, i);p0;}}int p1 p0;for(int i p0; i < n; i){if(nums[i] 1){swap(nums, p1, i);p1;}…...

网络协议--IP:网际协议
3.1 引言 IP是TCP/IP协议族中最为核心的协议。所有的TCP、UDP、ICMP及IGMP数据都以IP数据报格式传输(见图1-4)。许多刚开始接触TCP/IP的人对IP提供不可靠、无连接的数据报传送服务感到很奇怪。 不可靠(unreliable)的意思是它不能…...
【考研数学】线性代数第六章 —— 二次型(3,正定矩阵与正定二次型)
文章目录 一、基本概念1.1 引例1.2 正定二次型概念 二、正定二次型的判别写在最后 一、基本概念 1.1 引例 (1)二次型 f ( x 1 , x 2 , x 3 ) x 1 2 3 x 2 2 2 x 3 2 X T A X f(x_1,x_2,x_3)x_1^23x_2^22x_3^2\pmb{X^TAX} f(x1,x2,x3)x123…...

【Java 进阶篇】手把手教你创建 Bootstrap 旅游网站
随着互联网的普及,旅游行业在全球范围内迅速发展。人们通过网络规划、预订和分享他们的旅行经历。因此,拥有一个令人印象深刻的旅游网站对于吸引游客和提供有用信息至关重要。在本篇博客中,我们将手把手教您如何创建一个令人兴奋的旅游网站&a…...

一百九十二、Flume——Flume数据流监控工具Ganglia单机版安装
一、目的 在安装好Flume之后,需要用一个工具可以对Flume数据传输进行实时监控,这就是Ganglia 二、Ganglia介绍 Ganglia 由 gmond、gmetad 和 gweb 三部分组成。 (一)第一部分——gmond gmond(Ganglia Monitoring Da…...

光学知识整理-偏振光
偏振光 目录基础概念基础概念的补充平面偏振光(线偏振光)部分偏振光圆偏振光椭圆偏振光菲涅耳公式相位关系 反射折射所引起的偏振态的改变斯托克斯倒逆关系重要参数 目录 基础概念 光是横波:光是电磁波,其电场分量(电场强度)E、磁场分量(磁…...
CUDA纹理内存tex1D/tex2D/tex3D函数
CUDA的tex1D是用于从一维纹理中读取数据的函数。纹理是一种特殊的内存区域,可以用来存储图像、视频或其他数据。tex1D函数可以用于从纹理中读取数据,并将其传递给CUDA程序。 tex1D函数的语法如下: float tex1D(sampler_t sampler, float te…...

【Java基础面试三十八】、请介绍Java的异常接口
文章底部有个人公众号:热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享? 踩过的坑没必要让别人在再踩,自己复盘也能加深记忆。利己利人、所谓双赢。 面试官:请介绍Java的异常接口 …...
LabVIEW中的数据通信方法
LabVIEW中的数据通信方法 LabVIEW中包含多种数据通信方法,不同的方法适用于不同的场景。应该先了解概述,确保在应用程序中使用正确的数据通信方法。 数据通信类型: 数据流元素 缓冲接口 变量接口 应用场景: 在多数程序框图对…...
记调试SMBUS的心得
为什么电池电压读的不对 仔细一看是I2C读取数据的时候少了一个CLK I2C是非常严密的 读数据之后,发送 ACK,让从机准备数据 发送NACK,告诉从机别准备了 ACK和NACK的区别是啥,告诉你,就是NACK先拉高SDA,再…...

【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载
拷贝构造函数 概念 在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎 那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢? 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用…...

C++迭代器失效
在STL中,有些操作会导致迭代器失效,即之前获取的迭代器无法再安全地使用。这是因为这些操作可能会改变容器的结构,例如插入、删除元素等。 具体来说,以下情况下迭代器会失效: 1. 当插入或删除元素导致容器中的内存重新…...
LuatOS-SOC接口文档(air780E)--iotauth - IoT鉴权库, 用于生成各种云平台的参数
iotauth.aliyun(product_key, device_name,device_secret,method,cur_timestamp) 阿里云物联网平台三元组生成 参数 传入值类型 解释 string product_key string device_name string device_secret string method 加密方式,”hmacmd5” “hmacsha1” “hmacsha256”…...

2005.6-2018.6月中国企业OFDI微观数据
2005.6-2018.6月中国企业OFDI微观数据 1、时间:2005.6-2018.6 2、范围:公司 3、指标:Year、Month、Chinese Entity、 Quantity in Millions 、Share size、Transaction Party、Sector、Subsector、Country、Region、BRI 4、数据解释&…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
命令行关闭Windows防火墙
命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)方法二:CMD命令…...