iOS——Block与内存管理

news2024/11/15 17:15:11

需要内存管理的情况

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.coloradmin.cn/o/2108909.html

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

相关文章

FPGA开发:可编程逻辑器件概述

PLD 1、什么是PLD? PLD指Programmable Logic Device,翻译为"可编程逻辑器件"。是20世纪70年代发展起来的一种新的集成电路,是一种半定制的集成电路。 PLD具有逻辑功能实现灵活。集成度高、处理速度快的特点。 PLD就像是一个可定…

【Vue】pnpm创建Vue3+Vite项目

初始化项目 (1)cmd切换到指定工作目录,运行pnpm create vue命令,输入项目名称后按需安装组件 (2)使用vs code打开所创建的项目目录,Ctrl~快捷键打开终端,输入pnpm install下载项目…

IDEA运行Java程序提示“java: 警告: 源发行版 11 需要目标发行版 11”

遇到这个提示一般是在pom.xml中已经指定了构建的Java版本环境是11例如(此时添加了build插件的情况下虽然不能直接运行代码但是maven是可以正常打包构建)&#xff1a; <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><…

Vue初学-简易计算器

最近在学习Vue的指令&#xff0c;做了一个简易计算器&#xff0c;比较适合刚入门的人参考学习。用到的知识点有&#xff1a; 1.插值表达式 2.v-model&#xff0c;双向绑定、-、*、/、**等操作符 3.v-show&#xff0c;控制操作数2是否显示&#xff0c;乘方时不显示操作数2 4.met…

‌软媒市场—‌软媒市场自助发布平台引领数字营销新风尚

在当今这个信息爆炸的时代,数字营销已经成为企业推广品牌、提升知名度的关键手段。而在众多数字营销工具中,‌软媒市场自助发布平台以其独特的优势脱颖而出,成为众多企业的首选。今天,我们就来深入探讨一下软文媒体自助发布平台如何在软媒市场中发挥重要作用,以及其背后的5万家…

FRP代理(TCP通信)实验

攻击机器---公网机器&#xff08;FRP服务端&#xff09;-TCP传输rdp内容--内网机器&#xff08;FRP客户端&#xff09;--内网本地&#xff08;RDP服务&#xff09; FRP版本&#xff1a;0.49.0 公网IP&#xff08;FRP服务端&#xff09;&#xff1a;192.168.254.131 内网&…

Mindspore 初学教程 - 4. 数据集 Dataset

数据是深度学习的基础&#xff0c;MindSpore 提供基于 Pipeline 的 数据引擎&#xff0c;通过数据集 数据集&#xff08;Dataset&#xff09; 和 数据变换&#xff08;Transforms&#xff09; 实现高效的数据预处理。其中 Dataset 是 Pipeline 的起始&#xff0c;用于加载原始数…

# centos7 安装 mysql

centos7安装mysql 1、添加 mysql 官方 yum 存储库 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpmrpm -ivh mysql80-community-release-el7-3.noarch.rpm2、使用Yum安装MySQL服务器&#xff1a; sudo yum install mysql-server3、启动MySQL服务…

Redis集群技术2——redis基础

Redis安装 Redis 的安装相对简单&#xff0c;无论是 Windows、Linux 还是 macOS 系统&#xff0c;都有相应的安装方法。以下是针对不同操作系统的 Redis 安装简述。 1. Linux 系统安装 Redis 在 Linux 系统中安装 Redis 通常有多种方式&#xff0c;这里以 Ubuntu 和 CentOS 为…

配置阿里云千问大模型--环境变量dashscope

1 开通百炼 首先要进入到阿里云平台&#xff0c;然后进入百炼平台。 2 获取API-KEY 进入之后再右上角可以查看到自己的API-KEY&#xff0c;这个东西就是需要配置在环境变量里的。 点击查看就可以获取 3 配置DASHSCOPE环境变量 如果使用dashscope来进行千问大模型的API对…

速度滞后补偿控制

这里介绍的速度滞后补偿控制和我们前面介绍的前馈控制有所区别&#xff0c;前馈控制的前提是能够获取位置参考指令的速度或加速度信号。在无法获取位置参考指令的上述性息的前提下&#xff0c;我们可以采用速度滞后补偿控制提高机电伺服控制系统动态跟踪精度。前馈控制的一些基…

2024社区版IDEA springboot日志输出颜色

IDEA版本&#xff1a;IntelliJ IDEA 2024.1.4 (Community Edition) 1、纯白色终端 2、彩色终端 3、配置过程 1、打开配置 2、选择启动类 3、点击修改选项&#xff0c;勾选虚拟机选项 4、在虚拟机选项框输入以下代码 -Dspring.output.ansi.enabledALWAYS5、应用确定&#xff0…

NLP从零开始------18.文本中阶处理之序列到序列模型(3)

4.3 其他解码问题和解码技巧 贪心解码和束解码只是最基础的解码方法&#xff0c;其解码结果会出现许多问题。这里主要介绍3种常见问题&#xff0c;并简单介绍解决方案。 4.3.1 重复性问题 有时我们会发现序列到序列模型不断重复的输出同一个词。一个解决方案是解码时在所预测的…

GateWay三大案例组件

一、局部过滤器接口耗时&#xff08;LogTime&#xff09; 命名规则&#xff1a;以GatewayFilterFactory结尾编写接口耗时过滤器 Slf4j Component public class LogTimeGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {private static long timeSpan 0…

ruoyi-vue-pro快速修改的包名和选配功能板块

使用KIT进行构建 KIT是一个专门构建框架的网站&#xff0c;ruoyi-vue-pro也发布至KIT了&#xff0c;所以我们可以通过KIT快速的选配功能和修改报名等操作。 构建地址&#xff1a;http://www.goldpankit.com/space/service/install?space%E8%8A%8B%E9%81%93%E6%BA%90%E7%A0%8…

AI建模——AI生成3D内容算法产品介绍与模型免费下载

说明&#xff1a; 记录AI文生3D模型、图生3D模型的相关产品&#xff1b;记录其性能、功能、收费与免费方法 0.AI建模产品 Rodin MeshAnything Meshy 生成效果比较&#xff1a; Rodin效果最好、Meshy其次 1.Rodin 官网&#xff1a;gHyperHuman 支持&#xff1a;文生模型、…

TextIn ParseX:助力开发者解析版面元素信息

TextIn ParseX通用文档解析是一款大模型友好的解析工具&#xff0c;支持将pdf文档、jpg、img图像等文件快速转换为markdown格式&#xff0c;支持各类表格、公式解析&#xff0c;帮助大语言模型的数据清洗和文档问答任务。 产品特点 支持多种扫描内容&#xff1a;能良好处理各类…

[数据集][目标检测]西红柿缺陷检测数据集VOC+YOLO格式17318张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;17318 标注数量(xml文件个数)&#xff1a;17318 标注数量(txt文件个数)&#xff1a;17318 标…

ORA-24067: exceeded maximum number of subscribers for queue ADMIN.SMS_MT_QUEUE

临时处理办法&#xff1a; delete from aq$_ss_MT_tab_D; delete from aq$_ss_MT_tab_g; delete from aq$_ss_MT_tab_h; delete from aq$_ss_MT_tab_i; delete from aq$_ss_MT_tab_p; delete from aq$_ss_MT_tab_s; delete from aq$_ss_MT_tab_t; commit; 根本处理办法&#x…

IIS 反向代理模块: URL Rewrite 和 Application Request Routing (ARR)

需要设置iis反向代理的场景其实挺多的。例如websocket、Server Sent Events(SSE) 都需要反向代理。 对于需要临时放公网访问的应用&#xff0c;直接运行127.0.0.1的开发环境&#xff0c;然后通过反向代理访问127.0.0.1就可以了&#xff0c;省去麻烦的iis设置。 IIS 实现反向代…