swift指针内存管理-闭包的循环引用

news2025/8/7 3:35:05

swift指针&内存管理-引用

无主引用

和弱引用类似,无主引用不会牢牢保持引用的实例。但是不像弱应用,无主引用假定是永远有值的

当我们去访问一个无主引用的时候,总是假定有值的,所以就可能会发生程序的崩溃

如果两个对象的生命周期并不相关,使用weak

如果非强引用对象 拥有与强引用对象相同或更长的声明周期的话,则应使用 无主引用 unowned (也就是说 两个对象拥有关联 — unowned)

在这里插入图片描述

结果

IFLObj1 deinit

IFLObj2 deinit

obj1 先销毁,obj2后销毁, obj2与obj1有关联,IFLObj2的成员obj1 与 IFLObj1关联在一起,不是可选值

obj1的成员 obj2 是可选值,也就是说 obj1销毁,成员obj2也一定不存在了

因此,IFLObj2的成员 obj1可以用 无主引用 unowned

闭包循环引用

首先我们的闭包一般默认会捕获我们的外部变量

var mVar = 10
let closure1 = {
    mVar += 1
}
closure1()
print("mVar = \(mVar)")

结果

mVar = 11

从打印结果可以看出来

闭包内部对变量的修改将会改变外部原始变量的值

那同样就会有一个问题,如果我们在class内部定义一个闭包,当前闭包访问属性的过程中,就会对我们当前的实例对象进行捕获

class IFLObj1 {
    var a: Int = 21
    var b: String = "joewong"
    var mClosure: (() -> ())?
    
    deinit {
        print("IFLObj1 deinit")
    }
}

func testClosure() {
    let mObj1 = IFLObj1()
    mObj1.mClosure = {
        mObj1.a += 2
    }
}

testClosure()

IFLObj1 deinit 并未执行

在这里插入图片描述

控制台查看 mObj1 引用计数

2 = (1 << 33 == 2)

mObj1 的强引用计数 为 1

注意:lldb调试的时候,不能使用 po mObj1,

因为那会增加 mObj1的引用计数,对分析造成干扰,而应该采用 api Unmanaged.passUnretained, passUnretained意思就是不对 mObj1造成引用计数+1

我们对比下不给 IFLObj1 成员 mClosure 赋值的情况

在这里插入图片描述

没有对 IFLObj1 成员 mClosure 赋值的情况下

强引用计数为0, 无主引用为1

通过对比,闭包的初始化 就会对 所在类 实例对象进行捕获,而并不需要等到闭包执行时,才捕获

如果用po 直接查看的话,会看到 闭包初始化之后,引用计数为2,未初始化闭包前,引用计数为1

这样就可以解释,testClosure函数作用域内,引用计数为2,作用域结束之后,引用计数 - 1, 变为1,并未变成0,所以无法执行 deinit

而能这样去理解吗???

从逻辑上就可以推翻这种假设了,

那如果 直接po mObj1, po多次,就会增加多次引用,作用域结束的时候,引用计数-1,并没有减到0,deinit并没有执行,假设就是错的,不能这样简单取巧的方式去理解

为了更严谨,我们就需要知道 deinit 是如何被调用执行

分析deinit调用时机

我们先通过汇编查看以下 testClosure 作用域结束前的 汇编流程,然后再找线索切入源码查看

在这里插入图片描述

在这里插入图片描述

swift_bridgeObjectRelease 是 OC 与 swift之间的转换部分,我们现在的代码是swift,忽略掉这几个影响,直接跳转到 swift_release

进入swift_release 指令

在这里插入图片描述
在这里插入图片描述
这个时候指令跳转进入到 swift_release

在这里插入图片描述
在这里插入图片描述

关键线索出现, 可是试图通过这种方式去源码搜索关键字,分析源码逻辑,纯粹从逻辑理性角度去分析,结果就是 nothiing,什么也得不出来,除了得到一些自己想当然的垃圾逻辑,基本上都是错的,有主观臆想在里边

这个时候,需要借助于符号断点 + 推断流程 + 部分源码,当然了,要摒弃掉po mObj1 带来的引用计数的影响

单步符号调试+引用计数监测

为了更方便查看引用计数的变化,testClosure 作用域里,我们追加一个 mObj2的引用

deinit 敲上断点

在这里插入图片描述

在这里插入图片描述
下载这两个符号 重新调试 , testClosure 作用域结束前打开 下载的两个符号

在这里插入图片描述

testClosure作用域内,

mObj1 无主引用计数为1

强引用计数 为 1, [ 1 << 33, 在高32位显示为2]
在这里插入图片描述

arrayDestroy, 与目前我们关注的对象不符,忽略

在这里插入图片描述

此时,引用计数没有变化,因为还为执行 回收

在这里插入图片描述
在这里插入图片描述

引用计数没有变化

在这里插入图片描述

引用计数发生变化, 32位显示为1,为标识位,标识当前正在进行deinit

deinit 执行

再看下 swift_deallocObjectImpl 源码

在这里插入图片描述
在这里插入图片描述

至于 deinit 基于什么样的源码逻辑 调用执行,暂时可以放弃这个念头,太繁琐,没有直给的逻辑,但是从前面的调试流程,可以知道

deallocClassInstance 引用计数清零,deinit标识位设置为1, 然后调用deinit

回到之前的闭包循环引用问题

通过符号进入 swift_deallocObjectImpl

在这里插入图片描述

引用计数依然为2

在这里插入图片描述

此时 引用计数 显示的是 0

在这里插入图片描述

但是 deinit 标识位 并没有设置为1, deinit未执行

分析下来,所以关键是这个 第32位标识位 ,为1,才会调用deinit去执行

在以上 闭包初始化前提下的分析过程中,swift_deallocObject 并未执行,反而执行的是swift_slowDealloc

源码中有这样的逻辑

在这里插入图片描述

在这里插入图片描述

deinit 标识位 不为1,就没有机会执行 deinit

闭包捕获列表

默认情况下,闭包表达式从其周围的范围捕获常量和变量,并强引用这些值,我们可以使用捕获列表来显式控制如何在闭包中捕获值

在参数列表之前,捕获列表被写为用逗号括起来的表达式列表,并用方括号括起来。如果使用捕获列表,则即使省略参数名称,参数类型和返回类型,也必须使用关键字in

var a1 = 0
var h1 = 13.1
let closure1 = { [a1] in
    print("closure1, a1 = \(a1), h1 = \(h1)")
}
a1 = 10
h1 = 18.9
closure1()

结果

closure1, a1 = 0, h1 = 18.9

闭包在初始化时,就直接对 捕获列表中的参数进行了初始化,而并不是在闭包执行时才初始化参数列表

也就是说,closure1 在初始化时, 捕获列表中的 参数 a1就已经完成了初始化,这里的逻辑是
[let a1 = 0], 是个常量, 即使closure1执行时, 这个参数也不会再变了

而 非捕获列表中的变量,比如 h1的捕获 则发生在 closure1执行时,这时候就是实际h1的值了

如果改变一下

var a1 = 0
var h1 = 13.1
let closure1 = { [a1] in
    print("closure1, a1 = \(a1), h1 = \(h1)")
}
a1 = 10
h1 = 18.9
closure1()

结果

closure1, a1 = 10, h1 = 18.9

闭包-延长生命周期

class IFLObj1 {
    var a: Int = 21
    var b: String = "joewong"
    var mClosure: (() -> ())?
    
    deinit {
        print("IFLObj1 deinit")
    }
}

func testClosure() {
    let mObj1 = IFLObj1()
    mObj1.mClosure = { [weak mObj1] in
        mObj1!.a += 2
    }
    mObj1.mClosure!()
    print("------mObj1.a = \(mObj1.a)")

}

testClosure()

结果

------mObj1.a = 23

IFLObj1 deinit

类似于OC 的方式,在block 内部 声明强引用,延长生命周期

mObj1.mClosure = { [weak mObj1] in
    if let mObj1 = mObj1 {
        mObj1.a += 2
    }
}

api - withExtendedLifetime 延长生命周期

withExtendedLifetime(mObj1) {
    if let mObj1 = mObj1 {
        mObj1.a += 2
    }
}

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

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

相关文章

单片机的调试接口 JTAG SWD

JTAG-DP 和 SW-DP DP&#xff1f;debug port SW serial wire PA13 JTMS SWDIO ------JTAG 模式选择引脚&#xff08;JTMS&#xff09; PA14 JTCK SWCLK ------JTAG时钟引脚&#xff08;JTCK&#xff09; PA15 JTDI ------JTAG 数据输入引脚&#xff08;JTDI&#xff09; PB3 J…

基于VitePress创建组件文档

我们准备用vitepress做我们的组件文档&#xff0c;方便我们浏览组件&#xff0c;提供使用指南给用户。 安装VitePress 安装&#xff1a; yarn add -D vitepress创建第一个文档&#xff1a; mkdir docs && echo # Hello VitePress > docs/index.md增加脚本命令&a…

十大跑步运动耳机品牌排行榜,值得推荐的六款运动耳机

除了工作时间&#xff0c;大多数人群都喜欢去运动健身&#xff0c;戴着耳机放着喜爱的音乐&#xff0c;慢跑在城市的每个角落里&#xff0c;看着各种事物&#xff0c;悠然自得释放压力的同时还能更加有动力去运动&#xff0c;不得不说&#xff0c;运动确实能够让我们暂时忘却烦…

算法训练Day28 | LeetCode93.复原IP地址(回溯算法中的切割问题2);78 子集(每个节点都收集结果);90.子集II(子集问题+去重)

前言&#xff1a;算法训练系列是做《代码随想录》一刷&#xff0c;个人的学习笔记和详细的解题思路&#xff0c;总共会有60篇博客来记录&#xff0c;记录结构上分为 思路&#xff0c;代码实现&#xff0c;复杂度分析&#xff0c;思考和收获&#xff0c;四个方面。如果这个系列的…

通过DIN算法进行深度特征组合商品推荐 数据+代码(可作为毕设)

案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。 方法概述:…

美团SemEval 2022结构化情感分析跨语言赛道冠军方法总结

总第547篇2022年 第064篇美团语音交互部针对跨语言结构化情感分析任务中缺少小语种的标注数据、传统方法优化成本高昂的问题&#xff0c;通过利用跨语言预训练语言模型、多任务和数据增强方法在不同语言间实现低成本的迁移&#xff0c;相关方法获得了SemEval 2022结构化情感分析…

使用dispatchEvent解决重叠元素响应事件问题

.npm 下的缓存文件太多怎么办&#xff1f;.npm 下缓存的包长时间未清理&#xff0c;占用空间太大怎么办&#xff1f; 查看磁盘占用情况 linux 系统里&#xff0c;查看磁盘占用情况&#xff1a;df -h 1.查看单个目录磁盘占用情况du -sh /指定目录 2.查看所有目录的磁盘占用情况…

基于VGG与LSTM实现针对图片的问答任务 数据+代码 可以作为毕设

任务描述:本教程将通过深度学习的方式实现一个简单的视觉问答模型,视觉问答的任务内容是将一张图片和一个自然语言问题作为输入,结合这两种信息,机器生成一条自然语言答案。本教程通过数据准备,视觉问答模型构建,视觉问答模型训练,视觉问答模型评估,视觉问答模型预测等…

2022年亚太C题资料汇总更新目录

1.17版本更新内容&#xff1a; 为大家收集了一套网上的成品论文&#xff0c;切记只能借鉴&#xff0c;不可全抄 1.16版本更新内容&#xff1a; 根据半成品论文中提及的加分点&#xff0c;为大家收集了本次比赛中作图较为好看的方式。 1.15版本更新内容&#xff1a; 对上传…

Hbuilder出现 CR LF

今天打开Hbuilder编辑器发现&#xff0c;咦&#xff0c;怎么变成这个样子了&#xff0c;我设置了啥&#xff1f; 最终尝试寻找了半天&#xff0c;原来这是显示了换行符号 &#xff0c;取消这个勾选就OK

西门子S7-200 SMART(6ES7 288-1ST40-0AA0)相关与晨控智能CK-FR08-E00关于modbus tcp 通信配置指南

西门子S7-200 SMART(6ES7 288-1ST40-0AA0)相关与晨控智能CK-FR08-E00关于modbus tcp 通信配置指南 准备阶段 软件&#xff1a;STEP 7-MicroWIN SMART PLC:S7-200 SMATR (6ES7 288-1ST40-OAAO) 读卡器&#xff1a;CK-FR08-E00 交换机&#xff1a;标准POE交换机 电源&#x…

华为云开发者官网首页焕新升级,赋能开发者云上成长

摘要&#xff1a;近日&#xff0c;华为云开发者官网首页迎来全新改版升级。本文分享自华为云社区《华为云开发者官网首页焕新升级&#xff0c;赋能开发者云上成长》&#xff0c;作者&#xff1a; 华为云社区精选 。 近日&#xff0c;华为云开发者官网首页迎来全新改版升级&…

3.81 OrCAD软件绘制原理图时如何使用任意角度的走线?OrCAD软件怎么统一查看哪些元器件是没有PCB封装的?

笔者电子信息专业硕士毕业&#xff0c;获得过多次电子设计大赛、大学生智能车、数学建模国奖&#xff0c;现就职于南京某半导体芯片公司&#xff0c;从事硬件研发&#xff0c;电路设计研究。对于学电子的小伙伴&#xff0c;深知入门的不易&#xff0c;特开次博客交流分享经验&a…

UNI-APP_开发支付宝小程序注意事项与解决方法,支付宝小程序图片显示问题

一、编译后&#xff0c;微信小程序上图片图标显示正常&#xff0c;但是一到支付宝小程序图片图标显示就不正常如下图&#xff1a; 微信显示 支付宝显示 官方文档&#xff1a;https://opendocs.alipay.com/mini/component/image //修改前---会出问题 <image src"&qu…

WSL2编译ijkplayer

Windows 11 专业版 22H2 22621.819 应用商店安装 Ubuntu 22.04.1 LTS 控制面板——程序和功能——启用或关闭Windows功能——适用于 Linux的Windows子系统 Error: 0x800701bc WSL 2 ??? 升级WSL https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.ms…

香港阿里云服务器被攻击了怎么办

香港阿里云被流量攻击了&#xff0c;一说到防御攻击&#xff0c;很多用户可能会想到CDN、高防IP等防御产品&#xff0c;这个思路是不错的。但是香港阿里云相对于国内的阿里云而言&#xff0c;更多情况下是无法直接使用国内的CDN和高防IP来防御的。大部分用户在使用香港阿里云时…

Go运行时的内存分配器以及消耗指定大小的内存(C语言)

对于go语言在运行时的一些内存分配&#xff0c;想要详细的了解&#xff0c;我们会用到自带的runtime.MemStats&#xff0c;有很多具体的细节实现&#xff0c;而不是简单的只看任务管理器中的内存分配。 我们先来看下这个记录内存分配器的结构体 type MemStats struct {Alloc …

【新知实验室】腾讯云TRTC初体验

一、前言 今年腾讯云音视频发布了“三合一”的RT-ONE™网络。该网络整合了腾讯云实时通信网络&#xff08;TRTC&#xff09;、即时通信网络&#xff08;IM&#xff09;以及流媒体分发网络&#xff08;CDN&#xff09;三张网络&#xff0c;为业界最完整的音视频通信PaaS平台构建…

【App自动化测试】(八)三种等待方式——强制等待、隐式等待、显示等待

目录1. 为什么要添加等待&#xff1f;2. 三种等待方式3. 强制&#xff08;直接&#xff09;等待4. 隐式等待4.1 隐式等待说明4.2 隐式等待无法解决的问题5. 显式等待5.1 为什么要使用显示等待机制&#xff1f;5.1.1 Html文件加载顺序5.1.2 为什么要使用显示等待机制&#xff1f…

简化工作和生活的 7 个在线地图制作平台分享

地图制作是数据和艺术的结合&#xff0c;数据可以传达人们想要的信息&#xff0c;而艺术是传达信息的一种方式&#xff0c;两者的正确组合创造了完美的地图。 每个平台在功能和价格方面都是独一无二的&#xff0c;有人可能认为创建自定义地图需要高级制图知识或复杂的地理信息…