【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、数据解释&…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
