iOS - block变量捕获原理


block对变量的捕获


1:可以捕获不可以修改变量

  • 局部变量

2:可以捕获且可以修改变量

  • 全局变量
  • 静态变量
  • __block修饰的局部变量

原理分析:


1. 局部变量为什么可以被捕获确不能修改

int a = 10;
void (^blcok)() = [^{NSLog(@"%d",a);
} copy];a=20;blcok(); // log : a = 10
复制代码

结果应该大家都知道,但是为什么会这样呢?

我们用clang转化之后看看


从block定义来看

void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a)), sel_registerName("copy")); 
复制代码

block的实现是通过__ZMX__blockTest_block_impl_0结构体的构造方法来定义的,我们来看下这个结构体

struct __ZMX__blockTest_block_impl_0 {struct __block_impl impl;struct __ZMX__blockTest_block_desc_0* Desc;int a;__ZMX__blockTest_block_impl_0(void *fp, struct __ZMX__blockTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
复制代码

impt:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};
复制代码
isa:指向Class的指针
flags:一些标识
reserced:保留的一些变量
funcptr:函数指针
复制代码

__ZMX__blockTest_block_desc_0:

static struct __ZMX__blockTest_block_desc_0 {size_t reserved;size_t Block_size;
} __ZMX__blockTest_block_desc_0_DATA = { 0, sizeof(struct __ZMX__blockTest_block_impl_0)};
复制代码
reserced:保留的一些变量
size:内存大小
复制代码

__ZMX__blockTest_block_impl_0 构造方法

我们可以看到这个构造方法有四个参数

void *fp:函数指针
struct __ZMX__blockTest_block_desc_0 *desc: desc结构体
int _a: 变量
int flags=0:标识 可以不传
复制代码

我们通过简化block的定义:

void (*blcok)() = ((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a));
复制代码

可以看到,我们在定义的时候就已经将a作为参数传递进去了。也就是在定义的时候我们的block就获取到了a的值,而且不管后面怎么修改a的值。我们在block内部获取的a都是定义的时候传进来的值,这也就导致为什么block可以捕获局部变量却不可以修改的原因


2.1 全局变量 可以被捕获也可以修改

(void)blockTest
{void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok(); // log : 20} 
复制代码

我们用clang转化之后看看

一样的部分我就不重复了,我们可以看到这个时候定义blcok的构造函数是没有传入之前的参数a

我们调用block然后再去执行NSLog函数 = 上面__ZMX__blockTest_block_func_0函数,这时候a的值已经改为20了

static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_70ee3a_mi_0,a);}复制代码

很显然,在我们调用block的时候,如果你之前有修改a的值,那打印的一定是新值


2.2 静态变量 可以被捕获也可以修改

 (void)blockTest
{static int a = 10;void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok(); //log : 20}
复制代码

我们用clang转化之后看看

通过构造函数我们可以看到,这时候入参多了一个int *_a,传递的是a的地址了。打印的函数__ZMX__blockTest_block_func_0也一样,都是获取到同一内存地址上的值操作。so,我们既可以访问a同时也可以修改a了


2.3 __block修饰的变量 可以被捕获也可以修改

(void)blockTest
{__block int a = 10;void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok();// log : 20}
复制代码

我们用clang转化之后看看

哎!这时候的结构体__ZMX__blockTest_block_impl_0a变成了一个结构体指针。好奇怪,我们来看一下这个结构体

struct __Block_byref_a_0 {void *__isa;
__Block_byref_a_0 *__forwarding;int __flags;int __size;int a;
};
复制代码
isa: 指向Class指针
forwarding: 是指向a地址的指针
flags:标识
size:大小
a: 变量
复制代码

我们再来看一下 我们blockTest函数

static void _I_ZMX_blockTest(ZMX * self, SEL _cmd) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));(a.__forwarding->a) = 20;((void (*)(__block_impl *))((__block_impl *)blcok)->FuncPtr)((__block_impl *)blcok);}
复制代码

这时候变量a变成了一个__Block_byref_a_0结构体,可以看到我们初始化的时候给a的地址跟a的值都传进去了

a = 20 -> (a.__forwarding->a) = 20 再次赋值我们是通过修改a指向的内存地址上的value来修改a的值

打印函数

static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by refNSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_c9e1ad_mi_0,(a->__forwarding->a));}
复制代码

我们是通过先获取block捕获到的a的内存地址对应的value,然后打印出来

所以我们可以捕获并且修改a的值




笔者是一个刚入门iOS,对block的原理一直是望而却步。

这次终于鼓足干劲努力尝试一番,一定有很多的不足,希望大家不吝赐教!

有任何问题可以留言,或者直接联系QQ:346658618

希望可以相互学习,一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell 更好看的回显

#!/bin/shsource /etc/init.d/functionsaction "hello" /bin/true转载于:https://blog.51cto.com/itech/1768218

【ArcGIS风暴】ArcGIS中等高线高程标注/注记(打断/消隐)方法案例汇总

本文以案例的形式,图文并茂详细讲解在ArcGIS 10.6中,等高线高程标注、注记的方法。 文章目录 一、属性标注二、Maplex工具标注1. 使用Maplex标注引擎2. 标注转换为注记3. 要素轮廓线掩膜4. 使用掩膜选项进行绘制参考阅读: 【CASS精品教程】CASS9.1等高线的绘制完整案例教程 …

Blazor University (35)表单 —— 编写自定义验证

原文链接:https://blazor-university.com/forms/writing-custom-validation/编写自定义验证源代码[1]请注意,与有关 EditContext、FieldIdentifiers 和 FieldState[2] 的部分一样,这是一个高级主题。如前所述,FieldState 类保存表…

HTML 元素内部添加预加载

CSS: /*元素内部加载loading*/.innerLoading {height: 100%;width: 100%;display: flex;justify-content: center;align-items: center;}.innerLoading * {text-align: center;color: #737782cc;fill: #73777A;font-size: 1em !important;font-family: SimSun,SimHe…

Windows下怎样安装Tomcat

Tomcat 是开源的WEB应用容器,所以受到各位程序员和公司的亲赖。在这里给大家介绍一下如何在Windows环境下安装Tomcat绿色版本,希望能够对大家有帮助。 1.首先去Tomcat官网下载Tomcat软件,在百度中搜索Tomcat,进入英文网址http://tomcat.apach…

智能识别云服务端平台之神【合合信息TextIn】

一、前言 众所周知,随着互联网和人工智能的发展,我们非常多的场景需要用到智能“识别”功能,比如人脸识别、通用文字识别、表格识别、办公文档识别、身份证、名片、营业执照等国内外卡证文字识别等等,同时识别与理解面临的全球性技…

【ArcGIS微课1000例】0015:ArcGIS如何创建/自定义快捷键?

为了提高工作效率,强大的ArcGIS提供了很多快捷键,如访问 ArcMap 菜单命令、窗口操纵、刷新或暂停地图绘制、通过拖放进行移动或复制等等。本文在ArcGIS已有快捷键的基础之上,为了提高工作效率,讲解如何定制个性化的快捷键。 参考阅读:【ArcGIS风暴】ArcGIS快捷键大全 文章…

Bresenham 算法

1965 年,Bresenham 为数字绘图仪开发了一种绘制直线的算法,该算法同样使用于光栅扫描显示器,被称为 Bresenham 算法。 原理 算法的目标是选择表示直线的最佳光栅位置。Bresenhan 算法在主位移方向上每次递增一个单位。另一个方向的增量为 0…

Python高级特性——迭代(Iteration)

Python高级特性——迭代(Iteration) 1、给定一个集合list或者tuple,可以通过for …… in ……的语法来实现循环遍历,这个循环我们就叫做迭代 迭代list: >>> m [haha,hehe,heihei,gaga] >>> for li …

ML.NET 更新

点击上方蓝字关注我们(本文阅读时间:5分钟)ML.NET是一款面向.NET开发人员的开源,跨平台机器学习框架,可以将自定义机器学习集成到.NET应用中。我们很开心地向您介绍我们在过去几个月中所做的工作。ML.NET:https://dotnet.microsof…

Andriod之提示java.lang.SecurityException: getDataNetworkTypeForSubscriber导致程序奔溃

1、问题 修改targetSdkVersion 33 适配Android13后4G网络环境被其它app拉起来提示这个异常 2、原因 我们定位到代码在这行函数 telephonyManager.getNetworkType()Android11 的权限有关,由于缺少该权限导致无法访问接口而提示安全异常 3、解决办法 方法1:我们直接申请RE…

js-权威指南学习笔记7

第七章 数组 1、数组直接量的语法允许有可选的结尾的逗号,所以[ , , ]只有两个元素而非三个。 2、调用构造函数Array()创建数组时,传入一个参数时表示指定数组的长度。 3、所有的索引都是属性名,但只有在0~2^32-2之间的…

[译]基于GPU的体渲染高级技术之raycasting算法

[译]基于GPU的体渲染高级技术之raycasting算法 PS:我决定翻译一下《Advanced Illumination Techniques for GPU-Based Volume Raycasting》。像我翻译其他资料一样,只按我的需要和观点来翻译。有的部分详细翻译,附加注解,有的部分…

【GIS风暴】什么是地理空间智能(Geospatial AI)?

人工智能(Artificial Intelligence,AI)已经成为新技术革命下一阶段的热词,也成为未来产业的驱动力量。使用智能算法,数据分类和智能预测、分析,AI在很多领域将有一系列的工具来帮助解决问题。 将AI用于GIS这一具体的领域的分析、方法和解决方案,就叫地理空间智能(Geos…

JavaScript 清除图片背景颜色 使之透明

主要JS /**清除图片背景颜色 **/ function removeImgBg(img) {//背景颜色 白色const rgba [255, 255, 255, 255];// 容差大小const tolerance 60;var imgData null;const [r0, g0, b0, a0] rgba;var r, g, b, a;const canvas document.createElement(canvas);const cont…

day01基础部分

一、python是什么样的语言 1、编译型语言和解释型语言,python是解释型语言 1.1、编译型语言就是把源程序代码一次性翻译成机器码(计算机可识别的代码),然后交给计算机去运行,一般需经过编译(compile&#x…

WPF 制作 Windows 屏保

分享如何使用WPF 制作 Windows 屏保WPF 制作 Windows 屏保作者:驚鏵原文链接:https://github.com/yanjinhuagood/ScreenSaver框架使用.NET452;Visual Studio 2019;项目使用 MIT 开源许可协议;更多效果可以通过GitHub[1]|码云[2]下…

Java 定时线程

功能需求:项目启动时,后天起一个定时线程,每个小时跑一次,查出数据发邮件出来。 主要使用 public void schedule(TimerTask task, long delay)task被安排在delay(毫秒)指定的时间后执行。 public void sche…

Windows 7 下右键发送到菜单项没了

为什么80%的码农都做不了架构师?>>> 问题描述: 突然有一天,Windows 7 下右键发送到菜单项没了,如图所示: 问题原因 黑人问号脸? 转载于:https://my.oschina.net/taadis/blog/1591398

【ArcGIS微课1000例】0016:ArcGIS书签操作(添加书签、管理书签)知多少?

书签可以将地图数据的某一视图状态保存起来,以便在使用时打开书签,直接回到这一视图状态。可创建多个书签以便快速回到不同的视图状态,也可以对书签进行管理。 文章目录 1 创建书签2 管理书签注意:书签只针对空间数据,在【布局视图】中是不能创建书签的。 1 创建书签 可…