Swift unowned self leaking when 'owned' by a view being presented

Swift unowned self leaking when 'owned' by a view being presented

据我所知,在这种情况下,我遇到了 unowned self 泄漏,应该不会发生泄漏。让我举个例子,它有点做作,所以请耐心等待,我已经尽力做到最简单的情况。

假设我有一个在 viewDidLoad 上执行闭包的简单视图控制器:

class ViewController2: UIViewController {

    var onDidLoad: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onDidLoad?()
    }
}

和一个 class,ViewHandler,它拥有此视图控制器的一个实例,并使用无主引用将对通知函数的调用注入到其闭包中:

class ViewHandler {

    private let viewController2 = ViewController2()

    func getViewController() -> ViewController2 {
        viewController2.onDidLoad = { [unowned self] in
            self.notify()
        }
        return viewController2
    }

    func notify() {
        print("My viewcontroller has loaded its view!")
    }
}

然后,当它的视图控制器被另一个视图控制器呈现时,ViewHandler 在 nilled out 时泄漏:

class ViewController: UIViewController {

    private var viewHandler: ViewHandler?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        viewHandler = ViewHandler()
        self.present(viewHandler!.getViewController(), animated: true, completion: nil)

        viewHandler = nil // ViewHandler is leaking here.
    }
}

我知道这个例子可能看起来有点做作,但据我所知不应该有泄漏。让我尝试将其分解:

在呈现 ViewHandler.ViewController2 之前,所有权应如下所示:

ViewController -> ViewHandler -> ViewController2 -|
                       ^                          |
                       |_ _ _ _ unowned _ _ _ _ _ |

呈现 ViewHandler.ViewController2 后,所有权应如下所示:

         _______________________________
        |                               v
ViewController -> ViewHandler -> ViewController2 -|
                       ^                          |
                       |_ _ _ _ unowned _ _ _ _ _ |

取消 ViewHandler 后,所有权应如下所示:

         _______________________________
        |                               v
ViewController    ViewHandler -> ViewController2 -|
                       ^                          |
                       |_ _ _ _ unowned _ _ _ _ _ |

没有任何东西拥有 ViewHandler,它应该被释放。然而,情况并非如此,ViewHandler 正在泄漏。

如果我将注入到 onDidLoad 的闭包的捕获列表中的引用更改为 weak,则不会发生泄漏,并且 ViewHandler 会按预期释放:

func getViewController() -> ViewController2 {
    viewController2.onDidLoad = { [weak self] in
        self?.notify()
    }
    return viewController2
}

此外,我无法解释的事情是,如果我将引用保持为无主并使 ViewHandler 继承自 NSObject,则 ViewHandler 会按预期释放并且没有泄漏:

class ViewHandler: NSObject {

    private let viewController2 = ViewController2()

    func getViewController() -> ViewController2 {
        viewController2.onDidLoad = { [unowned self] in
            self.notify()
        }
        return viewController2
    }

    ....
}

谁能解释一下这是怎么回事?

按照我目前的理解,符合NSObjectProtocol的NSObject。这种对象是从具有成熟内存管理的Objective-C桥接而来的。而当你使用 class 时,我们大多数人仍在使用这种 class。如果您从 NSObject 构建 class 应该不会有什么坏处。

swift class 的管理似乎有点实验性,因为人们更喜欢尽可能使用 structure。所以有些行为出乎意料也就不足为奇了。

所以大家在选择swift class的时候,要根据这个经验多想想。但好的一面是它们可能会带来一些不同于 classic NSObject 的新的和稳定的特性。

为简单起见,将 vc2 作为私有变量删除即可。

class ViewHandler {

func getViewController() -> ViewController2 {

    let viewController2  = ViewController2()

    viewController2.onDidLoad = { [unowned self] in
        self.notify()
    }
    return viewController2
}

func notify() {
    print("My viewcontroller has loaded its view!")
}


}

在这种情况下,泄漏仍然存在。用unowned很难判断。实际上,viewHandler的所有权已经转移到vc2。

vc2 发布后,泄漏也消失了。这是一种临时泄漏。

  var firstTime: Bool = true

    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

      if firstTime{
        viewHandler = ViewHandler()
        let vc = viewHandler!.getViewController()
        self.present(vc, animated: true, completion: nil)

        viewHandler = nil // ViewHandler is leaking here.

           DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
              vc.dismiss(animated: true, completion: nil)
               // leaking is over.
           }
    }
    firstTime.toggle()
}

甚至具体,所有权被vc.onDidLoad占用。如果

     viewHandler = nil // ViewHandler is leaking here.

或者

     vc.onDidLoad?() // error "ViewHandler has been dealloc!!"

     vc.onDidLoad = nil. // There is no error here. 

所以你应该在这里处理。这样 "leaking" 问题就解决了。