如果 'self' = self 在使用 [weak self] 的闭包中,如果他们引用的对象被释放,防护者会让 'self' = self 导致崩溃吗?

Could a guard let `self` = self inside a closure that uses [weak self] cause a crash if the object they reference becomes deallocated?

提问人:daniel 提问时间:6/27/2022 最后编辑:daniel 更新时间:6/27/2022 访问量:734

问:

我看过一些关于堆栈溢出的问题的评论,关于使用 [弱自我] 和 [无主自我]。我需要确保我理解正确。

我正在使用最新的 Xcode - Xcode 13.4,最新的 macOS - macOS Monterey 12.4,并且我正在编写与 iOS 12.0 兼容到最新 iOS 15 的代码。

首先,我是否正确,对它有强烈引用的记忆归强引用所属的任何一个都拥有?

另外,这是我真正需要知道的,我想知道如果我在闭包开始时使用会发生什么,然后我有语句。我相信,如果条件成功,guard 语句会将引用 self holds 分配给 guard 语句部分中新声明的名为 self 的强引用。这是对的吗?[weak self] inguard let `self` = self else { return }let `self

这让我不禁要问,在关闭结束时会发生什么。我是否正确,即使较新的自我拥有强引用,一旦闭包中的代码执行了闭包中的最后一个语句,强引用指向的内存就会被释放,因为具有强引用的新自我是在闭包本身中声明的,并且与为该闭包分配的所有内存一起被释放。

我想我做对了。如果没有,请告诉我。

如果您愿意,我对任何其他信息或任何澄清感兴趣。

iOS Swift 内存管理 关闭

评论

0赞 JanMensch 6/27/2022
简单地说:使用只是一个检查。它不会导致闭包突然“强力”保留实例。它只是检查:“还在吗?好吧,很酷,让我们用它做一些工作”。仅此而已:)guard let self = selfself
0赞 daniel 6/27/2022
@Christophe是的。直到你对此发表评论,我才注意到这一点。哈哈。我删除了第二个“首先”。

答:

1赞 Christophe 6/27/2022 #1

如果闭包具有强引用,则只要闭包仍可访问,它就会阻止 ARC 释放对象。

和自我允许解决这种强大的所有权
但这意味着闭包的弱自我所引用的对象可以被去定义,而闭包仍然可以被调用。在这种情况下,闭包的 self 将引用 nil。
weakunowned

实际上,它只是检查引用是否仍然有效。无论如何,零都会使它失败。如果它是有效的引用,则不会更改捕获列表;您可以假设 self 将保持有效,直到当前执行的闭包返回。guard

实验证明

这里有一些代码来试验差异,使用一个跟踪初始化和取消初始化的类,并且可以返回一个带有 captured 的闭包:self

class T {
    var id:String
    init(_ name:String){
        id = name
        print ("INIT T object \(id)")
    }
    func getClosure() -> (Int)->Int {
        return { [weak self] (i:Int) in    
            guard let self = self else {
                print ("Instance vanished")
                return -1
            }
            print (i,self.id)
            return 0
        }
    }
    deinit {
        print ("DEINIT T object \(id)")
    }
}

让我们创造一个戏剧性的情况,使闭包可以在创建它的实例中幸存下来:

var oops:(Int)->Int = {(Int)->Int in 0}

func f1() {
    var t = T("f1_test")
    t.getClosure()(32)       // closure is disposed at the end of the function
}                            // t will be deinitialized in any case
func f2() {
    var t = T("f2_test")
    oops = t.getClosure()    // closure will be kept outside of scope
}                            // t can be disposed only if capture is not strong

f1()
f2()
oops(33)

让我们分析一下发生了什么。将导致一个实例将被取消初始化。这是因为在离开时不再引用闭包,因此也不再引用捕获的闭包,ARC 将终止它:f1()Tf1self

INIT T object f1_test
32 f1_test
DEINIT T object f1_test

调用更微妙,因为实例在离开时仍由闭包引用,并且闭包在全局变量中仍然存在。f2()Tf2

如果捕获很强,则不需要 ,因为 ARC 将确保实例仍然可用。您会看到 T 实例不会被取消初始化。guard

但是由于捕获是 ,因此在返回时没有对实例的强引用,并且它将被正确取消初始化。所以守卫会让你得到:weakTf2

INIT T object f2_test
DEINIT T object f2_test
Instance vanished

这证明 不会用强捕获替换闭包的弱捕获。guard let self = self

评论

0赞 daniel 6/27/2022
当你说“[我]可以假设自我将保持有效,直到当前执行的闭包返回”时,我相信你的意思是,当我在闭包开始时,并且陈述成功,那么在闭包中对自我的任何引用仍然是一个弱自我。你是这个意思吗?[weak self] inguard let `self` = self
0赞 Christophe 6/27/2022
@Daniel是的,这就是我的意思。我添加了一些代码来说明这一点,并展示它在实践中是如何工作的。
1赞 Federico Arvat 6/27/2022 #2

当你像这样写一个闭包时:

{ [weak self] in
    guard let self = self else { return }
    ...
}

您正在检查声明此闭包的类是否仍在分配,如果该类仍在分配中,则您将在闭包本身中创建对它的新强引用。

这样做是因为如果删除了对该类的所有其他强引用,则仍将在

guard let self = self else { return }

此时,您可以确定在闭包结束之前,声明闭包的类将被分配,因为引用位于闭包本身中。

所以,要回答你的问题,不,如果你写

guard let self = self else { return }

这个闭包不会崩溃,因为你有一个仍然活着的强引用。

不同的是,如果你使用[无主的自我]。无主的自我就像一个未包装的可选。如果你在不检查它的存在的情况下使用它,你就是在隐式地写作

self!.<something>

因此,如果在调用该闭包时解除了自我分配,它将崩溃。