iOS——Block与内存管理

news/2024/9/17 7:42:55/ 标签: ios

需要内存管理的情况

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.ppmy.cn/news/1522713.html

相关文章

初识Linux · 有关gdb

目录 前言: 1 预备知识 2 gdb的使用 前言: 当我们Linux学到了这里的时候,我们大概会有一种感觉是,从VS2022转战Linux,写代码对我们来说是一种重新构建读写代码的一个过程,从文本编辑器,到文…

P1781 宇宙总统

[题目通道](宇宙总统 - 洛谷) #include<bits/stdc.h> using namespace std; int main() {int n,QQ; string w"";string q;cin>>n;for (int i1;i<n;i) {cin>>q;int z q.size();int x w.size();if (z>x||(z>x&&q> w)) {wq;QQ…

Vite + Vue3 +Vant4出现Toast is not a function

今天写前端的时候出现了这个问题搞了我一会 搜集原因: 1:是vant版本的问题&#xff0c;Toast()的方法是vant3版本的写法&#xff0c;而我用的是vant4&#xff0c;vant4中的写法改成了showToast()方法&#xff0c;改正过来 import {showToast} from "vant"; 发现还是…

本地部署AI大模型

mac windows 同理 用到的软件 docker ollama 大家可以到官网下载 在ollama官网选择models&#xff0c;这里面有用到的大模型 我用到的是 qwen2 &#xff0c;这个对中文比较友好 注意&#xff0c;每个模型有不同的参数版本&#xff0c;参数量越大&#xff0c;对电脑配置越高&…

flutter文本输入框使用

在Flutter中&#xff0c;实现输入框一般使用TextField&#xff0c;通过设置它的属性给输入框和内部文字设置不同的样式。 Flutter 输入框实现简单例子 import package:flutter/material.dart;class MyEditPage extends StatelessWidget {const MyEditPage({super.key});overr…

与MySQL邂逅

MySQL安装捏~ 其实每次新学一样东西&#xff0c;安装永远是一个小坎 但是小问题啦 安装MySQL要用root账户&#xff0c;安装后普通用户也可以用捏 要安装MySQL先来看第一步&#xff01; 改bug&#xff01; Centos 卸载不要的环境 先康康有木有捏&#xff1a; mariadb就是…

不小心删除了 Android 手机上的短信?3 步流程恢复误删除的短信以及图片、视频、联系人

不小心删除了 Android 手机上的短信&#xff1f;别担心&#xff0c;Android 版奇客数据恢复工具可以帮助您通过简单的 3 步流程恢复已删除的短信以及图片、视频、联系人等。 如何在 Android 上恢复已删除的短信 不小心删除了 Android 手机上的短信&#xff1f;Android 版奇客数…

Django缓存

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) Django 5框…

Web3社交新经济,与 SOEX 实现无缝交易的高级安全性

出于充分的理由&#xff0c;安全性是交易中至关重要的考虑因素。每个人都应该确保自己的资金在交易时是安全的。由于 &#xff33;&#xff2f;&#xff25;&#xff38; 充当您与交易所的最佳连接&#xff0c;因此必须强调的是&#xff0c;该系统不会引发任何安全问题。 &a…

C语言程序设计(算法的概念及其表示)

一、算法的概念 一个程序应包括两个方面的内容: 对数据的描述:数据结构 对操作的描述:算法 著名计算机科学家沃思提出一个公式: 数据结构 +算法 =程序 完整的程序设计应该是: 数据结构+算法+程序设计方法+语言工具 广义地说,为解决一个问题而采取的方法和步骤…

帮招一名海康VM机器视觉工程师,工作地:苏州园区,行业:智能仓储自动化巨头,VM可以二次独立开发,岁数35岁以下,薪资18K+

工作职责&#xff1a; 能完成视觉系统的评估&#xff0c;合理的选择硬件配置&#xff0c;快速的完成软件功能开发和调试&#xff0c;并跟踪设备运转状况&#xff0c;保证设备稳定运行 能够清晰的理解客户某个站点的工艺需求&#xff0c;准确定位项目需求&#xff1b;能够根据需…

网络学习-eNSP配置VRRP

虚拟路由冗余协议(Virtual Router Redundancy Protocol&#xff0c;简称VRRP) VRRP广泛应用在边缘网络中&#xff0c;是一种路由冗余协议&#xff0c;它的设计目标是支持特定情况下IP数据流量失败转移不会引起混乱&#xff0c;允许主机使用单路由器&#xff0c;以及即使在实际…

HTTP 一、基础知识

一、概述 1、概述 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。HTTP 是一种应用层协议&#xff0c;是基于 …

微知-BIOS中的XHCI模式是什么意思?(usb3.0的扩展控制器影响usb3.0速率等选项)

XHCI “eXtensible Host Controller Interface” “可扩展主机控制器接口”。 英特尔公司开发的一个USB主机控制器接口&#xff0c;主要面向USB 3.0&#xff0c;同时也支持USB 2.0及以下版本的设备。 是usb3.0的核心部分。 有他表示主机支持usb3.0 三种模式&#xff1a;Smart …

效率神器Listary,附激活码

相信很多人都在用Everything&#xff0c;但是我更钟情于Listary&#xff0c;双击Ctrl即可实现软件调用&#xff0c;支持命令、文件搜索、网络搜索&#xff0c;妥妥的增效神器&#xff0c; 软件使用 文件查找时&#xff0c;双击Ctrl调用搜索框后再双击Ctrl&#xff0c;可以打开…

全国大学生数学建模竞赛全国奖项评阅工作规范(2023年修订稿)

为了适应新的形势,更好地促进全国大学生数学建模竞赛活动的健康发展,进一步提高全国奖项评阅工作的质量和公正、公平性,根据《全国大学生数学建模竞赛章程》和竞赛活动的现状,特制订本规范。 一、评阅组的组成 第一条 全国评阅专家组(以下简称评阅组)由全国大学生数学建…

达梦数据库管理员常用SQL(一)

达梦数据库管理员常用SQL(一) 数据库基本信息数据库参数信息表空间信息日志文件信息进程和线程信息会话连接信息SQL执行信息等待事件信息事务和锁信息数据库基本信息 --查询数据库内部版本号 select id_code; select build_version from v$instance; select * from v$versi…

【游戏安全】CheatEngine基础使用——如何对不同类型的数值进行搜索?如何破解数值加密找到想修改的数值?

游戏安全 不同数值类型的搜索破解简单数值加密 不同数值类型的搜索 可以在游戏中看到很精确的物品数量&#xff0c;但是在CE中却什么都扫不到。 这是因为他的数值类型可能并不是四字节的&#xff0c;在游戏中这个数值的机制是一个慢慢增长的数值&#xff0c;所以他很有可能是…

使用 docker 部署 kvm 图形化管理工具 WebVirtMgr

文章目录 [toc]前提条件镜像构建启动 webvirtmgr创建其他 superuser配置 nginx 反向代理和域名访问绑定 kvm 宿主机local sockettcp 连接 虚拟机创建创建快照虚拟机克隆删除虚拟机 kvm 官方提供了以下这些图形化管理&#xff0c;license 这块也提示了是商业版&#xff08;Comme…

利士策分享,如何规划多彩的大学生活?

利士策分享&#xff0c;学习规划多彩的大学生活 踏入大学&#xff0c;如同开启一场充满未知与可能的旅程。 为了让这段旅程不仅充满学术的熏陶&#xff0c;还洋溢着生活的多彩与人际的和谐&#xff0c;我们需要精心规划&#xff0c;积极行动。 一、多彩规划&#xff1a;点亮大学…