iOS——Block与内存管理

需要内存管理的情况

1、对象类型的auto变量。
2、引用了 __block 修饰符的变量。

三种block类型

全局类型 (NSGlobalBlock)

如果一个block里面没有访问普通局部变量(也就是说block里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局变量),那这个block就是__NSGlobalBlock__。__NSGlobalBlock__类型的block在内存中是存在数据区的(也叫全局区或静态区,全局变量和静态变量是存在这个区域的)。__NSGlobalBlock__类型的block调用copy方法的话什么都不会做。

栈类型 (NSStackBlock)

如果一个block里面访问了普通的局部变量,那它就是一个__NSStackBlock__,它在内存中存储在栈区,栈区的特点就是其释放不受开发者控制,都是由系统管理释放操作的,所以在调用__NSStackBlock__类型block时要注意,一定要确保它还没被释放。如果对一个__NSStackBlock__类型block做copy操作,那会将这个block从栈复制到堆上。

堆类型 (NSMallocBlock)

一个__NSStackBlock__类型block做调用copy,那会将这个block从栈复制到堆上,堆上的这个block类型就是__NSMallocBlock__,所以__NSMallocBlock__类型的block是存储在堆区。如果对一个__NSMallocBlock__类型block做copy操作,那这个block的引用计数+1。

特殊情况

在 ARC 环境下,编译器会自动将栈上的 block 复制到堆上。以下是会触发这种情况的四种情况:

作为函数返回值时

typedef void (^MyBlock)(void);MyBlock createBlock() {int localVar = 50;return ^{NSLog(@"Local variable: %d", localVar);};
}

这里返回的 block 是 NSMallocBlock 类型,因为它是作为函数返回值返回的。

赋值给强指针时

void testStrongPointerBlock() {int localVar = 60;void (^stackBlock)(void) = ^{NSLog(@"Local variable: %d", localVar);};void (^strongBlock)(void) = stackBlock;
}

strongBlock 是 NSMallocBlock 类型,因为它被赋值给一个强指针。

作为函数参数时

void executeBlock(void (^block)(void)) {block();
}void testFunctionParameterBlock() {int localVar = 70;void (^stackBlock)(void) = ^{NSLog(@"Local variable: %d", localVar);};executeBlock(stackBlock);
}

传递给 executeBlock 的 stackBlock 被复制到堆上,因此是 NSMallocBlock 类型。

作为 GCD 的参数时

void testGCDParameterBlock() {int localVar = 80;void (^stackBlock)(void) = ^{NSLog(@"Local variable: %d", localVar);};dispatch_async(dispatch_get_main_queue(), stackBlock);
}

stackBlock 作为 GCD 的参数时被复制到堆上,因此是 NSMallocBlock 类型。

__block关键字

__block 修饰的变量会被封装成一个结构体,而不是简单地复制值。这个结构体包含该变量的指针。
当 Block 从栈复制到堆时,__block 变量的引用也会被复制到堆上,并且 Block 会对其产生强引用,确保变量的生命周期和 Block 一致。
当 Block 从堆中移除时,会通过调用 dispose 函数释放 __block 变量,管理其内存。
比如有如下例子:

    __block int val = 0;//修改后的代码

转化为c++代码:

struct __Block_byref_val_0 {void *__isa;__Block_byref_val_0 *forwarding;int __flags;int __size;int val;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0 *Desc;__Block_byref_val_0 *val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,__Block_byref_val_0 *_val, int flags=0) : val(_val->__forwrding){impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;
}
};struct void __main_block_func_0(struct __main_block_impl_0 *__cself){__Block_byref_val_0 *val = __cself->val;printf("val = %d",val->__forwarding->val);
}

可以看出,在原来的block对象struct __main_block_impl_0中,多了一个 __Block_byref_val_0 *val;这个就是指向了封装了val信息的结构体的指针。因此任何对这个指针的操作,是可以影响到原来的变量的。

__Block_byref_val_0int val才是我们真正捕获到的val变量的值。实际上外部的val的地址也确实是指向这里的。所以不管是外面还是block里面修改age时其实都是通过地址找到这里来修改的。而且我们可以看见,在__Block_byref_val_0这个结构体中是有isa指针的,这就说明,我们实际上可以把它看作一个对象。

__block的内存管理方面的问题

既然是一个对象,那block内部如何对它进行内存管理呢?
当block在栈上时,block内部并不会对__Block_byref_val_0产生强引用。
当block调用copy函数从栈拷贝到堆中时,它同时会将__Block_byref_val_0也拷贝到堆上,并对__Block_byref_val_0产生强引用。
当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部又会调用_Block_object_dispose函数来释放__Block_byref_val_0

进一步,我们考虑截获的自动变量是Objective-C的对象的情况。在开启ARC的情况下,将会强引用这个对象一次。这也保证了原对象不被销毁,但与此同时,也会导致循环引用问题。

需要注意的是,在未开启ARC的情况下,如果变量附有_ _block修饰符,将不会被retain,因此反而可以避免循环引用的问题。

__forwarding指针

__block变量在栈上时, __forwarding 指向是自己本身的指针,可以取到值。
2、当__block变量在堆上时,__forwarding 指向也是自己本身的指针,可以取到值。
3、当__block变量从栈上复制到堆上时,_Block_object_assign 函数会对__block变量形成强引用(retain),此时栈上的 __forwarding 指向复制到堆上的 __block 变量的结构体指针。

在这里插入图片描述

在这里插入图片描述

__block的循环引用

__block的循环引用主要出现在block从栈复制到堆的时候,如果在block中使用用__strong修饰的对象时,在从栈复制到堆的时候就容易引起循环引用。
比如假如有一个block,它的成员变量在A类中被定义且为强引用,因此在这个类中,self是强引用这个block的,但是在这个block中它又使用了self,因此在这个block从栈复制到堆的时候,block会强引用self,self同时也在强引用block,此时就产生了循环引用。

请添加图片描述

因此这时,可以使用将self赋值给一个弱引用的id类型的变量,再在block中使用这个id类型的变量,使得block对self的引用为弱引用,因此来解决循环引用的问题。
请添加图片描述

还有一种方法是使用block来避免循环引用:就是在__block中将其强引用的对象置为nil。

在这里插入图片描述
请添加图片描述

解决方法总结:

  • ARC
  1. 使用__weak
  2. 使用__unsafe_unretained
  3. 使用__block解决(必须要调用block)
  • MRC
  1. 使用__unsafe_unretained
  2. 使用__block解决

block对象与OC对象相互持有(强引用) 才会造成相互循环引用

block对象持有__block变量对象 __block变量对象持有oc对象 oc对象持有block对象 构成3角循环引用

循环引用会导致实例对象不能释放 也就是实例对象所占用的内存不能及时被系统回收,会造成内存泄漏。

block如何截获变量的

假如有如下代码:

typedef void (^Block)(void);Block block;
{int val = 0;block = ^(){NSLog(@"val = %d",val);};
}
block();

在这段代码中,val是用block捕获的变量。
将其转化为cpp文件:


struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0 *Desc;int val;//这里多了一个名为val的变量//这里的构造函数会增加一个方法列表为val赋值,这里的val(_val)的意思就是val=_val__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,int _val, int flags=0) : val(_val){impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;
}
};struct void __main_block_func_0(struct __main_block_impl_0 *__cself){int val = __cself->val;printf("val = %d",val);
}

可以看出来,当使用block捕获了一个变量,首先会在__main_block_impl_0结构体中增加一个成员变量并且在结构体的构造函数中对变量赋值。以上这些对应着block对象的定义。
在block被执行的时候,把__main_block_impl_0结构体,也就是block对象作为参数传入__main_block_func_0结构体中,取出其中的val的值,进行接下来的操作。

delegate 和 block的区别

从源头上理解和区别block和delegate

  • delegate运行成本低,block的运行成本高。
    block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。

从使用场景区别block和delegate

  • 有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。当1,2个回调时,则使用block。
  • delegate更安全些,比如: 避免循环引用。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。

Block使用规范

在调用 Block 之前检查其是否为 nil:

在执行 Block 前,应先检查该 Block 是否存在(即不为 nil),以防止因调用空指针而导致程序崩溃。
OC对象函数与block调用在汇编层面上有区别,这种区别导致了对于block的调用需要进行判空后才能确保安全。如果调用的block是nil,程序会崩溃。
判空代码例如:

!block ?: block();

调用多层对象的block时,也需要进行判空,即使d对象与其block必然存在,也可能因为a、b、c对象中任意一个为nil,导致出现测试用例3的场景,调用一个nil对象的block产生崩溃,比如:

//不安全调用
a.b.c.d.block();//安全调用
!a.b.c.d.block ?: a.b.c.d.block();

对于这种情况,可以对将该block进行一层函数封装,可以避免过长的判断逻辑:

//d类
- (void)callBlock {!self.block ?: self.block();
}//调用
[a.b.c.d callBlock];

使用 Block 参数时判空:

在方法或函数内部使用 Block 参数时,也应先判空,确保安全调用。

两个问题:

  1. 为什么block中不能修改普通变量的值?
    由于无法直接获得原变量,技术上无法实现修改,所以编译器直接禁止了。

  2. __block的作用就是让变量的值在block中可以修改么?
    都可以用来让变量在block中可以修改,但是在非ARC模式下,__block修饰符会避免循环引用。注意:block的循环引用并非__block修饰符引起,而是由其本身的特性引起的。

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

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

相关文章

SpringBoot+Vue实现大文件上传(断点续传-后端控制(一))

SpringBootVue实现大文件上传(断点续传) 1 环境 SpringBoot 3.2.1,Vue 2,ElementUI,spark-md5 2 问题 在前一篇文章,我们写了通过在前端控制的断点续传,但是有两个问题,第一个问题&…

AUTOSAR Adaptive与智能汽车E/E架构发展趋势

AUTOSAR Adaptive是一个面向现代汽车应用需求的标准,特别适用于那些需要高计算能力和灵活性的应用。以下是AUTOSAR Adaptive的典型特性: 高计算能力:AUTOSAR Adaptive支持使用MPU(微处理器),这些处理器的性…

嵌入式开发学习路线(25届校招学习) 嵌入式学习路线七年规划:从大一小白到校招大佬 (学习路线汇总)

嵌入式开发学习路线(25届校招可以参考) 嵌入式系统作为当前最热门且最有发展前途的IT应用领域之一,吸引了大量有志于从事该行业的学习者。为了系统地掌握嵌入式开发技能,以下是一条详细的学习路线,旨在帮助初学者逐步…

CodeSys中动态切换3D模型

文章目录 需求研究结果 需求 在前面的【CodeSys开发3d机械臂显示控件】中,我们已经实现了一个可以显示3d模型的控件。但是这个控件是和使用的3d模型绑定死的,在安装这个控件时就已经将模型文件于控件一起安装到codesys中。 假如我想在不同的工程中&…

智能家居系统(基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现)

视频演示:基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目_哔哩哔哩_bilibili 基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目: https://pan.baidu.com/s/1f41gAfOOnlcQoKoMx3o84A?pwd6j2g 提取码: 6j2g 注:本项目为学习完…

Meta关闭Spark AR平台:未来规划与影响分析

Meta宣布将关闭其移动AR创作平台Spark AR,这一消息在业界引起了广泛关注。尽管Snap和TikTok在AR滤镜领域取得了巨大成功,但Meta却选择了另一条发展道路。本文将探讨这一决策背后的可能原因及其对未来的影响。 关闭Spark AR平台的背后 硬件为主&#xff…

计算机网络(三) —— 简单Udp网络程序

目录 一,初始化服务器 1.0 辅助文件 1.1 socket函数 1.2 填充sockaddr结构体 1.3 bind绑定函数 1.4 字符串IP和整数IP的转换 二,运行服务器 2.1 接收 2.2 处理 2.3 返回 三,客户端实现 3.1 UdpClient.cc 实现 3.2 Main.cc 实现 …

【Mysql】系统服务启动访问报错问题处理:this is incompatible with sql_mode=only_full_group_by

一、背景: 本来已经正常运行的平台,突然有一天由于对服务器进行部分操作迁移,发现jar可以正常启动,但是访问功能一直报错,监控后台日志后,发现了问题: 报错的具体信息如下: Caused…

Linux编译器--gcc/g++使用

目录 一、预编译指令 1.1预处理功能 1.2指令 1.3问题扩展 二、编译(生成汇编) 三、汇编(生成二进制机器语言) 四、链接(生成可执行文件或库文件) 4.1库文件 4.2目标文件和库的链接 4.3动态库和静态…

【Django-Minio-Storage 使用教程】

Django-Minio-Storage 使用教程 安装 Django-Minio-Storage配置 Django 项目官方文档 安装 Django-Minio-Storage 使用 pip 安装 Django-Minio-Storage pip install django-minio-storage配置 Django 项目 在 Django 项目的 settings.py 文件中进行以下配置 INSTALLED_APPS…

【mysql】mysql修改sql_mode之后无法启动

现象:修改后mysql无法启动,不报错 原因:MySQL在8以后sql_mode已经取消了NO_AUTO_CREATE_USER这个关键字。去掉这个关键字后,启动就可以了 修改前: sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR…

Bootstrap前端框架Glyphicons字体图标

115工具网收集提供Bootstrap前端框架Glyphicons字体图标库对照表​​​​​​​,Bootstrap前端UI,Glyphicons字体图标调用,Bootstrap按钮字体图标对照表,包括250多个来自Glyphicon Halflings的字体图标.项目中引用Bootstrap相关文件后即可直接调用下列图标class&quo…

Linux CentOS安装PySpark3.5(单机版)详细教程及机器学习实战

目录 一、安装须知 二、安装Spark 1、下载安装包 2、修改配置文件spark-env.sh 3、验证Spark是否安装成功 三、安装py4j 四、配置环境变量 五、基于PySpark的机器学习实战 1、将数据文件上传HDFS 2、创建代码文件 3、提交应用程序 一、安装须知 前置依赖&#xff1…

Acrobat Pro DC 2023 for Mac/Win:全能型PDF编辑器深度解析

Adobe Acrobat Pro DC 2023作为一款跨平台的PDF编辑器,无论是对于Mac还是Windows用户,都提供了极为全面且强大的PDF处理功能。该软件凭借其卓越的性能和丰富的特性,成为了全球范围内用户处理PDF文档的首选工具。 一、强大的编辑功能 Acroba…

【2024高教社杯全国大学生数学建模竞赛】ABCDEF题 问题分析、模型建立、参考文献及实现代码

【2024高教社杯全国大学生数学建模竞赛】ABCDEF题 问题分析、模型建立、参考文献及实现代码 1 比赛时间 北京时间:2024年9月5日 18:00-2024年9月8日20:00 2 思路内容 2.1 往届比赛资料 【2022高教社杯数学建模】C题:古代玻璃制品的成分分析与鉴别方案…

HBase 部署及shell操作

HBase 数据库 一、HBase 概述1.1 HBase 是什么HBase 的特点 二、HBase 模型及架构2.1 HBase 逻辑模型2.2 HBase 数据模型2.3 HBase 物理模型2.3.1 列簇物理模型2.3.2 Rowkey 字段排序2.3.3 Region 存储到不同节点2.3.4 Region 结构 2.4 HBase 基本架构 三、搭建 HBase 分布式集…

Claude的小白入门指南

要想快速上手Claude AI,其实并没有那么复杂。作为新一代的AI助手,Claude致力于为用户提供高效、无害、透明的交互体验。这篇入门指南将从Claude AI的特点、主要功能和如何实际操作等几个方面为大家做一个详细的介绍。 Claude AI是什么? Claud…

【SRC挖掘】越权漏洞——burp插件被动检测越权漏洞,一个插件让挖洞效率翻倍!Autorize

越权与未授权漏洞 越权漏洞什么是越权漏洞?Autorize插件安装使用步骤拦截过滤器 越权漏洞 什么是越权漏洞? 越权漏洞是指应用程序未对当前用户操作的身份权限进行严格校验,导致用户可以操作超出自己管理权限范围的功能,从而操作…

大模型笔记01--基于ollama和open-webui快速部署chatgpt

大模型笔记01--基于ollama和open-webui快速部署chatgpt 介绍部署&测试安装ollama运行open-webui测试 注意事项说明 介绍 近年来AI大模型得到快速发展,各种大模型如雨后春笋一样涌出,逐步融入各行各业。与之相关的各类开源大模型系统工具也得到了快速…

UnityShader自定义属性特性

前言: 在编写UnityShader时,我们常常会使用特性来更换材质球面板的属性外观,除此之外,还可以使用自定义的扩展脚本来实现自定义的材质球界面,参考我之前的文章UnityShaderUI编辑器扩展 但是自定义扩展每次都要单独写…