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

时间:2019-03-20 15:27:51

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

在我所知不应该发生泄漏的情况下,我正在经历一个无主的自我泄漏。让我举一个例子,它有点人为,所以请允许我,我已经尽力提出了最简单的例子。

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

class ViewController2: UIViewController {

    var onDidLoad: (() -> Void)?

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

和一个类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!")
    }
}

然后,当其视图控制器由另一个视图控制器呈现时,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
    }

    ....
}

谁能解释发生了什么事?

1 个答案:

答案 0 :(得分:-1)

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

class的管理似乎有点实验性,因为人们更愿意在可能的情况下使用swift class。因此,有些意外行为并不奇怪。

因此,当您选择structure时,您必须根据这种经验进行更多思考。但是好的一面是,它们可能会带来一些与经典NSObject不同的新的稳定功能。

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

swift class

在这种情况下,泄漏仍然存在。用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!") } } 来判断是很难的条件。实际上,viewHandler的所有权已转移到vc2。

释放vc2后,泄漏也消失了。这是暂时的泄漏。

unowned

即使是特定的,所有权也被 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!!"

所以您应该在这里处理。从而解决了“泄漏”问题。