【问题标题】:Swift unowned self leaking when 'owned' by a view being presentedSwift unowned self 当被呈现的视图“拥有”时泄漏
【发布时间】:2019-08-11 08:45:08
【问题描述】:

据我所知,在不应该发生泄漏的情况下,我遇到了无主自我的泄漏。让我举个例子,有点做作,所以请耐心等待,我已经尽量做到了最简单的情况。

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

class ViewController2: UIViewController {

    var onDidLoad: (() -> Void)?

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

还有一个类,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 在被取消时泄漏:

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 的闭包的捕获列表中的引用更改为弱,则不会泄漏,并且 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
    }

    ....
}

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

【问题讨论】:

    标签: swift memory-leaks presentmodalviewcontroller ownership-semantics unowned-references


    【解决方案1】:

    根据我目前的理解,符合NSObjectProtocol的NSObject。这种对象是从具有成熟内存管理的 Objective-C 桥接而来的。而当你使用class 时,我们大多数人仍在使用这种类。如果您从 NSObject 构建一个类,它应该不会受到伤害。

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

    所以当你选择swift class时,你要根据这个经验多考虑一下。但好的一面是它们可能会带来一些不同于经典 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. 
    

    所以你应该在这里处理。至此“泄露”的问题就解决了。

    【讨论】:

    • 它仍然没有解释为什么 ViewHandler 在没有对象持有对它的强引用时被泄露。我和你有同样的经历,如果我们发布 VC2,ViewHandler 就会停止泄漏。但这仅在 VC2 持有对 ViewHandler 的强引用时才有意义,但由于在 VC2 块中对 ViewHandler 的引用被声明为无主,因此不应如此。所以我的结论是,目前这是 Swift 中的一个错误。
    • 它没有泄漏,因为“ViewHandler 已被释放!!”您可能会注意到这里的原因是执行订单有问题,并且在运行期间已经概述了这种情况。它更像是一个“孤儿”对象。我们需要以正确且安全的方式处理 NIL 对象。主要原因是 onDidLoad() 是一个结构而不是类。所以一切都有一个副本,但你不能使用它。
    • 啊,我想我明白你的意思了。我还尝试在 deint() 中放置一个断点,正如你所说,它实际上正在被释放,但我猜它仍然对内存图可见。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2021-11-03
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多