Swift - 持有闭包引用泄漏

bau*_*sic -1 swift

编辑(讨论后):

SwiftUI持有是一种常见的View (struct)做法ObservableObject。由于 astruct不是引用类型,因此很容易忘记 is 保存着一个。

问题实际上应该是:

  1. 间接引用会导致保留周期吗?
  2. 保留循环可以涉及单个指针吗

两者的答案都是肯定的

  1. 结构 -> 类 -> 闭包{struct.class}
class ViewModel: ObservableObject
...
struct MyView: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        Button("Action") {
            viewModel?.handler {
                // ViewModel is retained here through self:
                // (MyView.viewModel)
                self.someAction()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 中SwiftUI,将自己“传递给”你的人并不是一个好习惯。ViewModel

  1. 持有指向自身的实例变量的类
    (这里讨论当外部头引用被移除时,ARC如何处理循环链表?
class A {
    var next: A
}
let a = A()
a.next = a // Retain cycle of an object to itself.
Run Code Online (Sandbox Code Playgroud)

(原问题)

为什么即使闭包不保留任何内容,持有对闭包的引用也会导致内存泄漏?

(这始于一个SwiftUI问题,但我不确定它是否真的相关)

所以这里有一个简单的 Manager ( ViewModel),它包含一个闭包。

class Manager: ObservableObject {
    
    private var handler: (() -> Void)?
    
    deinit {
        print("Manager deallocated")
    }
    
    func shouldDismiss(completion: @escaping () -> Void) {
        handler = completion
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.handler?()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个视图,使用它:

struct LeakingView: View {
    
    @ObservedObject var manager: Manager
    
    let shouldDismiss: () -> Void
    
    var body: some View {
        Button("Dismiss") { [weak manager] in
            manager?.shouldDismiss {
                self.shouldDismiss()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是ViewController激活流程:

class ViewController: UIViewController {

    var popup: UIViewController?
    
    @IBAction func openViewAction(_ sender: UIButton) {
        
        let manager = Manager()
        
        let suiView = LeakingView(manager: manager) { [weak self] in
            self?.popup?.view.removeFromSuperview()
            self?.popup?.dismiss(animated: true)
            self?.popup = nil
        }
        
        popup = LKHostingView(rootView: suiView)
        
        
        popup?.view.frame.size = CGSize(width: 300, height: 300)
        view.addSubview(popup!.view)
    }
    
}
Run Code Online (Sandbox Code Playgroud)

当运行这个时,只要管理器持有闭包,它就会泄漏并且Manager不会被释放。
保留周期在哪里?

mat*_*att 5

为什么即使闭包不保留任何内容,持有对闭包的引用也会导致内存泄漏?

因为闭包确实保留了一些东西。它保留了self.

例如,当你说

        manager?.shouldDismiss {
            shouldDismiss()
        }
Run Code Online (Sandbox Code Playgroud)

第二个shouldDismiss意思self.shouldDismiss是并保留self。因此,LeakingView 保留了 Manager,但 Manager 通过闭包现在保留了 LeakingView。保留循环!

这就是为什么人们[weak self] in一开始就说这样的关闭。

  • 在这种情况下,如果“shouldDismiss”是不可变的,您可以直接捕获它,而不是捕获所有“self”。 (2认同)
  • 好吧,但是在这种情况下,`self`是一个`struct`,我们真的可以'保留'它吗,它不只是复制吗?如果是这样,我们能做什么(因为我们不能使用“weak”作为非引用变量)? (2认同)