swift指针内存管理-闭包的循环引用
创始人
2024-02-14 13:02:32

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 = 21var 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] inprint("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] inprint("closure1, a1 = \(a1), h1 = \(h1)")
}
a1 = 10
h1 = 18.9
closure1()

结果

closure1, a1 = 10, h1 = 18.9

闭包-延长生命周期

class IFLObj1 {var a: Int = 21var b: String = "joewong"var mClosure: (() -> ())?deinit {print("IFLObj1 deinit")}
}func testClosure() {let mObj1 = IFLObj1()mObj1.mClosure = { [weak mObj1] inmObj1!.a += 2}mObj1.mClosure!()print("------mObj1.a = \(mObj1.a)")}testClosure()

结果

------mObj1.a = 23

IFLObj1 deinit

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

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

api - withExtendedLifetime 延长生命周期

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

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...