一顿饭的功夫带你了解block是如何内存管理的

简介: 一顿饭的功夫带你了解block是如何内存管理的

1、这里先思考一个问题,为什么是block内部自行内存管理呢?


根据谁创建谁销毁的原则,因为对象在block结构体中,由结构体持有,那么block就要自行管理什么时候持有对象,什么时候销毁对象。


2、这里还需要思考一个问题,什么情况下需要管理内存,什么情况下不需要管理内存。


2.1、不需要内存管理的情况:


1、当block在栈上:block内部不会对__block变量产生强引用,所以不需要内存管理

2、基本数据类型,没有加 __block 修饰符的:block内部是值存储,不涉及对象管理,所以不需要内存管理。

3、static变量和全局变量:内存放在数据段,由程序统一管理,可全局访问,长期持有而且不会销毁,所以不需要内存管理。


除了以上几个情况,那么只剩下堆区的数据,需要进行内存管理了。


2.2、需要进行内存管理的情况:


1、对象类型的auto变量。

2、引用了 __block 修饰符的变量。


1、底层结构分析内存管理


那么block内部又是怎么进行内存管理的呢?下面开始一一揭晓。


一言不合先上代码,分析一下,加和不加 __block 修饰符,底层结构的异同点:


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      // 对象类型的 auto 变量
        NSObject *obj = [[NSObject alloc] init];
        // 加了 __block 修饰符的基本数据类型的变量
        __block int age = 10;
        void(^block)(void) = ^{
            NSLog(@"Hello:%@",obj);
            NSLog(@"Hello:%d",age);
        };
        block();
    }
    return 0;
}


底层代码结构:


// 1、程序入口
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *obj = objc_msgSend(objc_msgSend)(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        // 2、基本数据类型age变量,这里被封装成age结构体对象类型
        __Block_byref_age_0 age = {0,&age, 0, sizeof(__Block_byref_age_0), 10};
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, &age, 570425344));
        block->FuncPtr(block);
    }
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;//3、这里添加了auto变的内存管理操作
  NSObject *obj;// 4、与main中的对象类型一致
  __Block_byref_age_0 *age; // 5、__block 修饰之后,这里封装好了的结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 2.1、当block从栈copy到堆上的时候,会执行这个copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  // 如果是没有__block修饰符的对象类型:其引用类型同外部传进来的保持一致
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    // 添加了 __block修饰符的变量,一定是强引用关系
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
// 2.2、当 block 销毁的时候,会调用这个 dispose 函数,把block结构体内部持有的变量一起销毁。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
// 5、__block 修饰之后,这里封装好了的结构体
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};


根据底层结构,用更形象的图进一步分析:


2、当block被copy到堆时

----> 会调用block内部的copy函数

--------> copy函数内部会调用_Block_object_assign函数

------------> _Block_object_assign函数会对__block变量形成强引用(retain)

59cb24cb30bb40f1bc107f2284be59af.png


f950239067354a2f8dd4a314fbff239d.png


3、当block从堆中移除时

----> 会调用block内部的dispose函数

--------> dispose函数内部会调用_Block_object_dispose函数

------------> _Block_object_dispose函数会自动释放引用的__block变量(release)

354d575abb3d4c1e805a3bb6a3433028.png

8a1993af57bd414aaba56c3e985af1fb.png


4、对象类型的auto变量 VS 添加__block修饰符的变量

相同点:

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


2、当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*/);


3、当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*/);


不同点:

1、添加__block修饰符的变量,在block结构体中都是强引用。

2、对象类型的auto变量,根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 (注意:这里仅限于ARC时会retain,MRC时不会retain)

719ff587386543b3a5b9d75b42f92a33.png


5、__block的__forwarding指针

从前面的示例代码我们发现,__block修饰的age变量,被封装成结构体类型 __Block_byref_age_0:


struct __Block_byref_age_0 {
  void *__isa;// isa地址指针
__Block_byref_age_0 *__forwarding;// 指向自己的指针
 int __flags;
 int __size;
 int age;//变量
};


这里有一个很奇怪的情况,为什么还要有一个 __forwarding 指向自己的指针呢?

其实这里涉及到了__block变量从栈上被copy到堆上情形,具体逻辑见下图:

92ee08300e964caa9883c660a63aece5.png


1、当__block变量在栈上时, __forwarding 指向是自己本身的指针,可以取到值。

2、当__block变量在堆上时,__forwarding 指向也是自己本身的指针,可以取到值。

3、当__block变量从栈上复制到堆上时, _Block_object_assign 函数会对__block变量形成强引用(retain),此时栈上的 __forwarding 指向复制到堆上的 __block 变量的结构体指针,没毛病。


相关文章
|
数据采集 机器学习/深度学习 资源调度
归一化和标准化
归一化和标准化
|
9月前
|
机器学习/深度学习 人工智能 弹性计算
阿里云GPU云服务器怎么样?云服务器性能、应用场景及收费标准和活动价格参考
阿里云GPU云服务器通过GPU与CPU协同计算,为人工智能、高性能计算等领域提供强大支持,具备覆盖广、计算能力强、网络性能出色等优势,适用于直播转码、图片渲染、AI训练推理等场景,单实例可提供高达1000 TFLOPS混合精度计算性能。其计费方式灵活,包括包年包月、按量付费等。2026年特惠活动期间,新人可享T4、V100卡最低包月5折起,目录价直降最高25%,用户可结合优惠券进一步降低成本,实现高效上云。
830 7
|
人工智能 JSON API
淘宝/天猫:使用物流查询API实时显示包裹位置,减少客服咨询量
在电商竞争激烈的环境下,淘宝、天猫通过集成物流查询API,实现实时追踪包裹位置,显著减少用户咨询量。本文解析其原理、实现步骤与效益,展示如何以技术手段提升用户体验、降低客服压力,助力平台高效运营。(238字)
|
10月前
|
存储 SQL 监控
SpringBoot- 整合Logback,滚动记录+多文件
`logback-spring.xml` 是 Spring Boot 项目中的日志配置文件,用于定义日志输出格式、级别及存储路径。支持控制台与文件双输出,按时间滚动日志,并分类记录如 SQL、错误、请求参数等信息,便于问题排查与系统监控。
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
604 0
|
传感器 人工智能 边缘计算
“种田也能上5G?”——带你看懂5G+智慧农业的真相与技术细节
“种田也能上5G?”——带你看懂5G+智慧农业的真相与技术细节
697 19
|
存储 人工智能 搜索推荐
Shandu:开源AI研究黑科技!自动挖掘多层级信息,智能生成结构化报告
Shandu 是一款开源的 AI 研究自动化工具,结合 LangChain 和 LangGraph 技术,能够自动化地进行多层次信息挖掘和分析,生成结构化的研究报告,适用于学术研究、市场分析和技术探索等多种场景。
1264 8
Shandu:开源AI研究黑科技!自动挖掘多层级信息,智能生成结构化报告
|
运维 数据挖掘 API
官方api和第三方api有什么区别
官方API与第三方API是电商商家常用的两种接口类型。官方API由电商平台提供,功能全面、集成度高、安全性强且稳定性好,适合处理核心业务;而第三方API由外部服务商提供,专注特定功能如支付、物流等,灵活性和扩展性更强,但安全性和稳定性参差不齐。商家应根据自身需求、预算及技术能力选择合适的API,确保数据安全与合规性,同时优化运营流程、提升用户体验并拓展业务功能,在竞争中占据优势。
1155 0
|
机器学习/深度学习 人工智能 物联网
微软Phi-4系列开源:多模态与文本处理的创新突破
微软近期推出 Phi-4-multimodal 和 Phi-4-mini,这些模型是 Microsoft Phi 系列小型语言模型 (SLM) 中的最新模型。Phi-4-multimodal 能够同时处理语音、视觉和文本,为创建创新且具有上下文感知能力的应用程序开辟了新的可能性。另一方面,Phi-4-mini 在基于文本的任务方面表现出色,以紧凑的形式提供高精度和可扩展性。
1112 4
|
监控 Java
Java文件夹复制解决方案:优化大文件与大量数据的处理
Java中复制文件夹及其内容,尤其是当处理大文件或文件夹(如几个GB)时,需要特别注意内存使用和性能优化。以下是一个详细的指导,包括如何避免内存溢出异常,并确保复制过程的高效性。
619 1

热门文章

最新文章