当前位置: 首页 > news >正文

【iOS】Block补充

前言

笔者之前寒假有学习过block的相关知识,但当时学得并不是很明白,现在看源码正好回顾了block的相关知识,这篇文章来补充一些之前没有学习到的知识或者博客中没有记录的知识(之前的博客——OC高级编程之Blocks)

block捕获外界变量

在寒假的学习中,我们知道了block捕获自动变量值时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例中,但当时不知道是怎么样保存到结构体中的,也不知道为什么外部对自动变量的改变不会影响到block内部。这里从自动变量、静态局部变量、全局以及全局静态变量四种情况来分析block如何捕获外界变量

捕获自动变量

我们在Block中使用localA局部变量,block会对localA这个变量进行捕获,代码运行结果如下:

可见在外部修改localA并不会影响到Block内部

因为对于自动变量,在block的初始化函数中,localA 通过拷贝与 struct __main_block_impl_0 绑定,也就是说 localA 是拷贝过来

Block 捕获基本数据类型(如 int、float 等)或结构体时,它会通过值捕获的方式复制变量的当前值。这意味着在 Block 内部使用的是变量捕获时刻的快照。由于是值复制,所以之后即使原始变量的值发生改变,Block 内部的值也不会改变。

捕获静态局部变量

捕获静态局部变量时,静态局部变量与Block建立关联的是指针,也就是说Block捕获的静态局部变量捕获的是变量的指针,因此当我们对静态局部变量进行修改时,Block内部的静态局部变量的值也会随之改变

全局、全局静态变量

全局变量以及全局静态变量不会出现在block底层的结构体中,意味着二者无法被捕获。其实,当block中需要使用全局变量时,直接使用就可以了,从逻辑上来说也确实不必要捕获。

判断block存储在哪里

当block中访问自由变量时,block底层的类对象为_NSStackBlock_或者_NSMallocBlock_。

需要注意这里就算不调用copy方法,创建Block变量并且捕获自由变量时Block会自动被拷贝到堆上,如果没有copy操作使用完就直接释放了,如果不创建Block变量就没有copy操作

是否创建block变量访问变量类型Block类型备注
自由变量_NSStackBlock_
自由变量_NSMallocBlock_NSStackBlock执行了copy操作

Block_copy()源码分析

void *_Block_copy(const void *arg) {return _Block_copy_internal(arg, WANTS_ONE);
}
​

查看实际调用的函数:

/* 拷贝 Block,或者增加 Block 的引用计数。若需要拷贝,调用拷贝协助方法(如果存在) */
static void *_Block_copy_internal(const void *arg, const int flags) {struct Block_layout *aBlock;const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
​///1、若不存在源 Block ,则返回 NULLif (!arg) return NULL;///2、将源 Block 指针转换为 (struct Block_layout *)aBlock = (struct Block_layout *)arg;///3、若源 Block 的 flags 包含 BLOCK_IS_GC,则其为堆块。 \/// 此时增加其引用计数,并返回这个源 Blockif (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on highlatching_incr_int(&aBlock->flags);return aBlock;}///4、源 Block 是全局块,直接返回源 Block(全局 Block 就是一个单例)else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;}
​///5、源 Block 是一个栈 Block,执行拷贝操作。首先申请相同大小的内存struct Block_layout *result = malloc(aBlock->descriptor->size);if (!result) return (void *)0;///6、使用 memmove 方法将栈区里的源 Block 逐位复制到刚申请的堆区 Block 内存中。这样做是为了保证完全复制所有元数据。memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first///7、更新 result 的 flags。result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed    ///Junes 确保引用计数为 0。注释表示没这个必要,可能因为此时引用计数早已为 0。但是为了防止 bug 被保留下来。result->flags |= BLOCK_NEEDS_FREE | 1;  ///Junes 为 result 的 flags 添加 BLOCK_NEEDS_FREE,并设置其引用计数为 1。表明这是一个堆 Block(一旦引用计数降为 0,则其内存将被回收)///8、将 result 的 isa 指向 _NSConcreteMallocBlock。这意味着 result 是一个堆 Block。result->isa = _NSConcreteMallocBlock;///9、如果 result 存在拷贝协助方法,调用它。/// 如果 block 捕获对象,编译器将会生成这个协助方法。/// 这个协助方法将会 retain 被捕获的对象。if (result->flags & BLOCK_HAS_COPY_DISPOSE) {(*aBlock->descriptor->copy)(result, aBlock); }return result;
}

源码中对copy操作进行了分类

1、如果源Block不存在则返回NULL 2、如果源 Block 是 NSConcreteMallocBlock,增加其引用计数,然后返回源 Block; 3、如果源 Block 是 _NSConcreteGlobalBlock,直接返回源 Block,因为NSConcreteGlobalBlock是一个单例; 4、如果源 Block 是 _NSConcreteStackBlock,那么操作就比较复杂

申请一块相同大小的内存 拷贝栈上的block的所有元数据到新申请的内存空间上,也就是将数据拷贝到堆上,堆上的block我们叫做result 更新 result 的 flags,确保其引用计数为 0; 更新 result 的 flags,添加 BLOCK_NEEDS_FREE,并设置其引用计数为 1; 将 result 的 isa 指向 _NSConcreteMallocBlock。标明 result 是一个堆 Block; 如果 result 捕获了对象,调用编译器生成的拷贝协助方法 retain 被捕获的对象。

block被拷贝时,block中使用的变量也会一起拷贝到堆区,总结如下:

对象类型拷贝方式
操作对象内存直接指针拷贝*dest = object
__weak直接指针拷贝*dest = object
Block实例通过_Block_copy()复制
OC变量引用计数+1
__block结构体通过_Block_byref_assign_copy()复制该结构体

ARC环境下,一旦使用__block修饰并在block中修改,就会触发copy操作,block就会从栈区copy到堆区,此时的block堆区block

将 block 赋值给 strong 类型的属性或变量也会触发copy操作

Block循环引用

上图两段代码中,代码一就是一段会造成循环引用的代码,self持有block,而block又使用了self的属性name,因此block会持有self,这样就导致了block和self的互相持有

而代码二中虽然也使用了name属性,但是self不持有block,因此不会发生循环引用

解决循环引用可以使用以下几种方式:

  • 方式①: weak-strong-dance -- 强弱共舞

  • 方式②: __block修饰对象(需要注意的是在block内部需要置空对象,而且block必须调用)

  • 方式③: 传递对象self作为block的参数,提供给block内部使用

  • 方式④: 使用NSProxy(这个笔者暂时还没弄明白,就不细说了)

weak-strong-dance

如果block内部并未嵌套block,直接使用__weak修饰self即可

此时weakSelfself指向用一片内存空间,且使用__weak不会导致self的引用计数发生变化

如果block内部嵌套block,需要同时使用__weak__strong

其中strongSelf是一个临时变量,在block的作用域内,即内部block执行完就释放strongSelf

这种方式叫做中介者模式,weakSelf作为中介者传递引用,strongSelf代理执行

__block修饰对象

这种方式思路是将self作为__block变量,在block中使用完后再将指针置为空手动释放

需要注意的是这里的block必须调用,如果不调用blockvc就不会置空,那么依旧是循环引用,selfblock都不会被释放.

传递对象self作为block的参数

这个思路就是把对象self作为参数给block使用,就不会有计数问题

http://www.xdnf.cn/news/1323847.html

相关文章:

  • RecSys:排序中的融分公式与视频播放建模
  • 数据结构(03)——线性表(顺序存储和链式存储)
  • 从哲学(业务)视角看待数据挖掘:从认知到实践的螺旋上升
  • 常见的光源频闪控制方式
  • CSDN转PDF【无水印且免费!!!】
  • 数字时代著作权侵权:一场资本与法律的博弈
  • Gartner发布2025年AI与网络安全成熟度曲线:用AI增强网络安全计划的27项技术与创新
  • C++ const
  • Swift 实战:判断点集是否关于某条直线对称(LeetCode 356)
  • Effective C++ 条款48:认识模板元编程
  • 【前端面试题】JavaScript 核心知识点解析(第一题到第十三题)
  • 【Python语法基础学习笔记】条件表达式和逻辑表达式
  • 03.文件管理和操作命令
  • 网站服务器使用免费SSL证书安全吗?
  • 免费又强大的 PDF 编辑器 ——PDF XChange Editor
  • MacOS 安全机制与“文件已损坏”排查完整指南
  • 【Tech Arch】Spark为何成为大数据引擎之王
  • 算法题打卡力扣第26. 删除有序数组中的重复项(easy))
  • Linux 中断机制深度分析
  • 【轨物交流】轨物科技与华为鲲鹏生态深度合作 光伏清洁机器人解决方案获技术认证!
  • nuScence数据集
  • 特种行业许可证识别技术:通过图像处理、OCR和结构化提取,实现高效、准确的许可证核验与管理
  • Android Cutout(屏幕挖孔)详解
  • Python day48.
  • 【笔记ing】考试脑科学 脑科学中的高效记忆法
  • OCR库pytesseract安装保姆级教程
  • Zephyr下控制ESP32S3的GPIO口
  • 飞算JavaAI家庭记账系统:从收支记录到财务分析的全流程管理方案
  • 上下文切换及线程操作相关内容
  • 微信小程序通过uni.chooseLocation打开地图选择位置,相关设置及可能出现的问题