swift指针&内存办理-引证

无主引证

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

当咱们去拜访一个无主引证的时分,总是假定有值的,所以就可能会发生程序的崩溃

假如两个目标的生命周期并不相关,运用weak

假如非强引证目标 具有与强引证目标相同或更长的声明周期的话,则应运用 无主引证 unowned (也便是说 两个目标具有相关 — unowned)

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

成果

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 并未履行

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

操控台检查 mObj1 引证计数

2 = (1 << 33 == 2)

mObj1 的强引证计数 为 1

注意:lldb调试的时分,不能运用 po mObj1,

由于那会增加 mObj1的引证计数,对剖析形成搅扰,而应该采用 api Unmanaged.passUnretained, passUnretained意思便是不对 mObj1形成引证计数+1

咱们比照下不给 IFLObj1 成员 mClosure 赋值的状况

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

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

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

经过比照,闭包的初始化 就会对 地点类 实例目标进行捕获,而并不需要等到闭包履行时,才捕获

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

这样就能够解说,testClosure函数效果域内,引证计数为2,效果域完毕之后,引证计数 – 1, 变为1,并未变成0,所以无法履行 deinit

而能这样去了解吗???

从逻辑上就能够推翻这种假定了,

那假如 直接po mObj1, po屡次,就会增加屡次引证,效果域完毕的时分,引证计数-1,并没有减到0,deinit并没有履行,假定便是错的,不能这样简略取巧的方法去了解

为了更谨慎,咱们就需要知道 deinit 是如何被调用履行

剖析deinit调用机遇

咱们先经过汇编检查以下 testClosure 效果域完毕前的 汇编流程,然后再找线索切入源码检查

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

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

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

进入swift_release 指令

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

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

这个时分指令跳转进入到 swift_release

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

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

关键线索呈现, 但是企图经过这种方法去源码查找关键字,剖析源码逻辑,纯粹从逻辑理性角度去剖析,成果便是 nothiing,什么也得不出来,除了得到一些自己想当然的废物逻辑,基本上都是错的,有片面臆想在里边

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

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

为了更方便检查引证计数的改动,testClosure 效果域里,咱们追加一个 mObj2的引证

deinit 敲上断点

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

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

下载这两个符号 重新调试 , testClosure 效果域完毕前翻开 下载的两个符号

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

testClosure效果域内,

mObj1 无主引证计数为1

强引证计数 为 1, [ 1 << 33, 在高32位显现为2]

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

arrayDestroy, 与现在咱们重视的目标不符,疏忽

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

此刻,引证计数没有改动,由于还为履行 收回

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

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

引证计数没有改动

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

引证计数发生改动, 32位显现为1,为标识位,标识当前正在进行deinit

deinit 履行

再看下 swift_deallocObjectImpl 源码

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

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

至于 deinit 基于什么样的源码逻辑 调用履行,暂时能够抛弃这个想法,太繁琐,没有直给的逻辑,但是早年面的调试流程,能够知道

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

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

经过符号进入 swift_deallocObjectImpl

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

引证计数仍然为2

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

此刻 引证计数 显现的是 0

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

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

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

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

源码中有这样的逻辑

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

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

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
    }
}