简体   繁体   English

呈现视图“拥有”时,Swift无主自我泄漏

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

I am experiencing a leak with unowned self under conditions where, to the best of my knowledge, there shouldn't be a leak. 据我所知,在不存在泄漏的情况下,我正在经历一个无主的自我泄漏。 Let me show an example, it is a little contrived, so bear with me, I've tried to make the simplest case I could. 让我举一个例子,它有点人为,所以请允许我,我尝试过尽可能简单的情况。

Suppose I have a simple view controller that executes a closure on viewDidLoad: 假设我有一个简单的视图控制器,它对viewDidLoad执行闭包:

class ViewController2: UIViewController {

    var onDidLoad: (() -> Void)?

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

and a class, ViewHandler, that owns an instance of this view controller and injects a call to a notify function into its closure, using an unowned reference: 还有一个ViewHandler类,它拥有该视图控制器的实例,并使用一个未拥有的引用将对notify函数的调用注入到其闭包中:

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!")
    }
}

Then, when its view controller is presented by another view controller, the ViewHandler is leaking when nilled out: 然后,当它的视图控制器由另一个视图控制器呈现时,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.
    }
}

I know the example may seem a little contrived, but as far as I know there shouldn't be a leak. 我知道该示例可能看起来有些虚构,但据我所知应该不会泄漏。 Let my try and break it down: 让我尝试分解一下:

Before presenting ViewHandler.ViewController2, ownership should look like this: 在展示ViewHandler.ViewController2之前,所有权应如下所示:

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

After presenting ViewHandler.ViewController2, ownership should look like this: 显示ViewHandler.ViewController2后,所有权应如下所示:

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

After nilling out ViewHandler, ownership should look like this: 在取消ViewHandler之后,所有权应如下所示:

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

Nothing is owning ViewHandler and it should be released. 没有任何东西拥有ViewHandler,应该将其释放。 However this is not the case and ViewHandler is leaking. 但是,事实并非如此,ViewHandler正在泄漏。

If I change the reference in the capture list of the closure injected into onDidLoad to weak, there is no leak and ViewHandler is released as expected: 如果将注入onDidLoad的闭包的捕获列表中的引用更改为弱,则不会发生泄漏,并且按预期方式释放ViewHandler:

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

Also, something I can't explain, if I keep the reference as unowned and make ViewHandler inherit from NSObject, ViewHandler is released as expected and there is no leak: 另外,我无法解释的是,如果我保持引用为不拥有并让ViewHandler从NSObject继承,则ViewHandler将按预期方式释放,并且不会泄漏:

class ViewHandler: NSObject {

    private let viewController2 = ViewController2()

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

    ....
}

Anyone who can explain what going on? 谁能解释发生了什么?

According to my current understanding, the NSObject which conforms to NSObjectProtocol. 根据我目前的理解,符合NSObjectProtocol的NSObject。 This kind of objects was bridged from Objective-C which has a mature memory management. 这种对象是从具有成熟内存管理功能的Objective-C桥接的。 And when you use class , most of us still using this kind of class. 当您使用class ,我们大多数人仍在使用此类。 It should not hurt if you are build a class from NSObject. 如果您是从NSObject构造一个类,则应该不会受到伤害。

The management of swift class seems working with a little experimental style as people prefer using a structure whenever possible. swift class的管理似乎有点实验性的风格,因为人们倾向于尽可能使用structure So it's not strange there is some behaviors unexpected. 因此,有些意外行为并不奇怪。

So when you choose swift class , you have to think more according to this experience. 因此,当您选择swift class ,您必须根据这种经验进行更多思考。 But the good side is they may bring some new and stable features which is different from classic NSObject. 但是好的一面是,它们可能会带来一些与经典NSObject不同的新的稳定功能。

To make it simple, just remove vc2 as a private variable. 为了简单起见,只需删除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!")
}


}

In this case, the leak still exists. 在这种情况下,泄漏仍然存在。 It's a hard condition to judge with unowned . unowned判断是很难的条件。 Actually, the ownership of viewHandler has transferred to vc2. 实际上,viewHandler的所有权已转移到vc2。

When vc2 is released, the leaks are also gone. 释放vc2后,泄漏也消失了。 It's kind of temporary leak. 这是暂时的泄漏。

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

Even specific, the ownership is occupied by vc.onDidLoad . 甚至是特定的,所有权也由vc.onDidLoad占据。 If 如果

     viewHandler = nil // ViewHandler is leaking here.

either 要么

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

or 要么

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

So you are suppose to handle here. 因此,您应该在这里处理。 And thus the "leaking" problem has been solved. 从而解决了“泄漏”问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM