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

小码哥iOS学习笔记第十天: __block和block内存管理

一、block内部修改外部变量的值

1、通过static修饰的变量

  • static修饰的变量, 在block内可以修改变量的值

  • 原因是因为, 在底层block捕获的是age的地址, 而不是age存储的数据

2、全局变量

  • 全局变量可以直接在block中修改值

  • block不会捕获全局变量, 而是直接使用, 所以可以直接改值

3、__block修饰的auto变量

问: 为什么__block修饰的变量, 可以在block内修改值?

二、__block

  • __block可以解决block内部无法修改auto变量值的问题

  • __block不能修饰全局变量和静态变量(static), 只能修饰auto变量

  • 查看上面代码在底层的结构

  • 可以看到在main函数中, __block int age = 20在底层是红框中的代码, age被包装成了__Block_byref_age_0类型的结构体

  • 查看block的结构, 可以看到block中捕获到的是__Block_byref_age_0类型的指针age, 如下图

  • __Block_byref_age_0中有五个成员变量, 这五个变量在main函数中创建age时传入参数
    • void *__isa;: 0
    • __Block_byref_age_0 *__forwarding;: age的地址(自身地址)
    • int __flags;: 0
    • int __size;: __Block_byref_age_0占用内存大小
    • int age;: __block修饰的变量age的值

  • 当block调用时,会通过age->__forwarding->age找到__Block_byref_age_0中的成员变量, 并修改值

  • 这就是为什么__block会修改block内部auto变量的值

我们在main函数中使用的age, 究竟是被包装的__Block_byref_age_0还是__Block_byref_age_0里面的成员变量age?

  • 通过查block底层的代码可以看到main函数中定义的变量age已经被包装成了一个__Block_byref_age_0对象, 而block在底层是__main_block_impl_0类型
  • 所以我们可以通过类型转换来查看block

  • 通过控制台打印age的地址

三、内存管理

1、基本数据类型的auto变量

  • 当栈上block复制到堆上时, 会直接将捕获的基本数据类型变量复制到堆中

  • 底层结构如下

2、对象类型的auto变量

  • 当block复制到堆上时, 如果捕获的auto变量是对象类型, 那么就会有两种情况
__strong修饰的变量

  • 底层结构如下

  • 当变量被强引用修饰时, block复制到堆上的过程中会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数, 对被捕获的对象变量进行强引用

  • 当block从堆上移除时, 会调用block中的dispose函数,dispose函数内部会调用里面的_Block_object_dispose函数,将对被捕获对象变量的强引用断开

__weak修饰的变量

  • 底层结构如下

  • 当对象变量被__weak修饰时, block从栈中复制到堆中, 依然会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数
  • 只不过不会再对被__weak修饰的变量进行强引用

3、__block修饰的auto变量

  • 底层结构如下

  • __block修饰的变量在底层会被包装成一个__Block_byref_age_0对象, block会捕获__Block_byref_age_0对象
  • block从栈上复制到堆上时, 就会调用__main_block_desc_0中的copy函数, 对__Block_byref_age_0对象进行强引用
  • block从堆上移除时, 就会调用__main_block_desc_0中的dispose函数, 断开对__Block_byref_age_0对象的强引用

总结:
block在栈上时, 并不会对__block变量产生强引用
当block被copy到堆时
1.会调用block内部的copy函数
2.copy函数内部会调用_Block_object_assign函数
3._Block_object_assign函数会对__block变量形成强引用

4、__block__forwarding指针

  • block执行时,会先通过block->age拿到age对象, 然后通过age->__forwarding->age对象拿到age变量

  • 当block在栈上时, 通过__forwarding指针拿到的是栈中的__block结构体

  • 当block在堆上时, 通过__forwarding指针拿到的是堆中的__block结构体

5、对象类型的auto变量、__block变量总结

  • block在栈上时,对它们都不会产生强引用

  • block拷贝到堆上时,都会通过copy函数来处理它们 __block变量(假设变量名叫做a_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • block从堆上移除时,都会通过dispose函数来释放它们 __block变量(假设变量名叫做a) _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

四、被__block修饰的对象类型

  • 创建Person类, 添加age属性, 并重写dealloc方法

  • 使用__block修饰Person类型的变量

  • 查看底层结构如下

  • main函数中, 可以找到创建__block对象的代码, 可以看到传入了两个函数地址__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131

  • 已知, block捕获__block变量时, 会使用强引用
  • 而在__block修饰的变量会包装在__Block_byref_person_0
  • 所以, block的结构如下

  • block中包含struct __Block_byref_person_0 *person;, struct __Block_byref_person_0中包含person对象

  • block成员变量__main_block_desc_0结构体中的copydispose是用来处理personstruct __Block_byref_person_0之间连线的

  • __block对象中的copydispose, 是用来处理struct __Block_byref_person_0__strong person[[Person alloc] alloc]对象之间连线的

  • block对象在复制到堆中时, __Block_byref_person_0中的__Block_byref_id_object_copy函数指针调用, 最终调用下面的方法, 用来处理是否对person对象进行强引用
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    
}
复制代码
  • 当block从堆中移除时, __Block_byref_person_0中的__Block_byref_id_object_dispose函数指针调用, 最终调用下面的方法, 用来断开对person对象的强引用
static void __Block_byref_id_object_dispose_131(void *src) {
    
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    
}
复制代码
  • 在ARC中, block复制到堆中时是否会对被包装的auto对象变量进行强引用, 也要看__block修饰的是__strong类型还是__weak类型

1、__block修饰__strong类型的对象类型变量

  • __block修饰__strong类型的对象类型变量, 会对对象类型变量进行强引用

2、__block修饰__weak类型的对象类型变量

  • __block修饰__weak类型的对象类型变量, 会对对象类型变量进行弱引用

3、MRC环境下, __block修饰__strong类型的对象类型变量

  • 修改为MRC环境

  • 可以发现, MRC下, __block修饰的__strong类型的对象类型变量, 在block复制到堆上时,不会进行retain处理
  • person离开作用域会被释放

总结:
__block变量在栈上时,不会对指向的对象产生强引用

__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain

如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)\

五、循环引用

  • 新建命令行项目, 添加Person类, 添加block属性和age属性, 如下图

  • personblock中使用了person对象本身, 那么就会形成循环引用, 代码如下

  • 此时内存中的结构如下

  • main函数执行完, 局部变量Person *person会被释放, 此时内存中会存留personblock, 这就造成了block的循环引用问题

  • 想要解决这个问题, 只需要打断两条强引用中的一条即可, 而我们需要personblock属性存在, 所以只需要打断blockperson的强引用

1、ARC下解决block循环引用问题

  • 使用__weak__unsafe_unretained都可以使blockperson只进行弱引用
    • __weak: 当person被释放时, block中的__weak person会指向nil
    • __unsafe_unretained: 当person被释放时, block中的__unsafe_unretained person不会指向nil, 造成野指针
  • 以__weak为例, 代码如下

  • 此时的内存结构如下, block中的__weak personperson只是弱引用

  • main函数执行完,person就会被释放

  • 除了__weak__unsafe_unretained之外, 还可以使用__block来解决循环引用问题

  • 现有如下代码

  • 此时内存中的结构如下

  • 如果想使用__block解决block的循环引用问题, 只需要打断__block变量对象这根线的强引用关系

  • 此时代码如下, block必须被调用

  • 内存结构如下, 因为没有了__block变量到对象的强引用, 所以对象可以被释放

2、MRC下解决block循环引用问题

  • 可以使用__unsafe_unretained关键字修饰对象类型的auto变量, 来解决强引用问题

  • 还可以使用__block来修饰对象类型的auto变量, 此时已然可以解决循环引用问题

  • 这主要是因为
    • 在ARC下, __block会将修饰的对象类型的auto变量进行强引用
    • 在MRC下, __block不会将修饰的对象类型的auto变量进行强引用

转载于:https://juejin.im/post/5c820f8df265da2d9c389f35

相关文章:

  • Java获取电脑IP、MAC、各种版本
  • Mysql索引分析:适合建索引?不适合建索引?【转】
  • scrapy中间件源码分析及常用中间件大全
  • [蓝桥] 算法提高 简单加法
  • WEB FARM NLB TEST
  • 第二周
  • Availability Check Control (Checking Rule )
  • 单变量线性回归
  • Json对象与Json字符串互转
  • P2278 操作系统
  • 在国内最好的一个垂直类的“慕课”
  • ECOUX 姚梓鹏:资本寒冬下我开了一家设计公司 | 点评家
  • linux基础知识总结
  • yum [Errno 14] HTTP Error 404 - Not Found
  • 架构师必备技能:掌握JVM科学调优
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • Android Volley源码解析
  • Android 架构优化~MVP 架构改造
  • Android开源项目规范总结
  • Create React App 使用
  • github从入门到放弃(1)
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • iOS小技巧之UIImagePickerController实现头像选择
  • java第三方包学习之lombok
  • Nodejs和JavaWeb协助开发
  • php面试题 汇集2
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Python学习之路13-记分
  • Redis 懒删除(lazy free)简史
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • Unix命令
  • vue 配置sass、scss全局变量
  • Webpack 4 学习01(基础配置)
  • 测试如何在敏捷团队中工作?
  • 简析gRPC client 连接管理
  • 蓝海存储开关机注意事项总结
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 微服务入门【系列视频课程】
  • 学习使用ExpressJS 4.0中的新Router
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • #HarmonyOS:基础语法
  • %check_box% in rails :coditions={:has_many , :through}
  • (31)对象的克隆
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (四)图像的%2线性拉伸
  • (五)Python 垃圾回收机制
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .gitignore文件设置了忽略但不生效
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET 8.0 发布到 IIS
  • .NET LINQ 通常分 Syntax Query 和Syntax Method