这个视图控制器是否在“willSet / didSet”对中泄漏了?

时间:2017-01-27 16:21:52

标签: swift memory-leaks uiviewcontroller

你有一个vc(绿色),它有一个面板(黄色)“持有人”

enter image description here

假设您有十个不同的视图控制器...价格,销售,库存,卡车,驱动程序,调色板,您将放入黄色区域,一次一个。它将从故事板中动态加载每个VC

 instantiateViewController(withIdentifier: "PricesID") as! Prices

我们将在current中保留当前的VC。这里的代码可以让你在它们之间“交换”...

>>注意,这是错误的。请勿使用此代码<<

必须要做Sulthan在下面解释的事情。

var current: UIViewController? = nil {
    willSet {
        // recall that the property name ("current") means the "old" one in willSet
        if (current != nil) {
            current!.willMove(toParentViewController: nil)
            current!.view.removeFromSuperview()
            current!.removeFromParentViewController()
            // "!! point X !!"
        }
    }
    didSet {
        // recall that the property name ("current") means the "new" one in didSet
        if (current != nil) {
            current!.willMove(toParentViewController: self)
            holder.addSubview(current!.view)
            current!.view.bindEdgesToSuperview()
            current!.didMove(toParentViewController: self)
        }
    }
}

>>>>>>>>!重要<<<<<<<<<

另请注意,如果您执行此类操作,则在绿页完成时摆脱黄色视图控制器是必不可少的。否则current会保留它,绿色页面永远不会被释放:

override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    current = nil
    super.dismiss(animated: flag, completion: completion)
}

继续,您将使用current属性,如下所示:

func showPrices() {
    current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
}
func showSales() {
    current = s.instantiateViewController(withIdentifier: "SalesID") as! Sales
}

但请考虑一下,注意“点X”。通常在那里你一定要设置你正在摆脱的视图控制器为零。

blah this, blah that
blah.removeFromParentViewController()
blah = nil

但是我(不要认为)你真的可以在“willSet”代码块中将当前设置为nil。而且我很欣赏它即将被设置为某种东西(在didSet中)。但似乎有点奇怪。少了什么东西?你甚至可以在计算属性中做这种事情吗?

最终可用版本.....

使用Sulthan的方法,经过大量测试后,这种方法可以完美运行。

所以这样打电话

// change yellow area to "Prices"
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices

// change yellow area to "Stock"
current = s.instantiateViewController(withIdentifier: "StickID") as! Stock

这很有效......

var current: UIViewController? = nil { // ESSENTIAL to nil on dismiss
    didSet {
        guard current != oldValue else { return }

        oldValue?.willMove(toParentViewController: nil)
        if (current != nil) {
            addChildViewController(current!)

            holder.addSubview(current!.view)
            current!.view.bindEdgesToSuperview()
        }
        oldValue?.view.removeFromSuperview()

        oldValue?.removeFromParentViewController()
        if (current != nil) {
            current!.didMove(toParentViewController: self)
        }
    }
    // courtesy http://stackoverflow.com/a/41900263/294884
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
   // ESSENTIAL to nil on dismiss
    current = nil
    super.dismiss(animated: flag, completion: completion)
}

2 个答案:

答案 0 :(得分:3)

让我们将问题分为两个:(1)是否存在“泄漏”? (2)这是个好主意吗?

首先是“泄漏”。简答:不。即使你没有将current设置为nil,它所拥有的视图控制器显然也不会“泄漏”;当包含的视图控制器不存在时,current指向的视图控制器也是如此。

然而,current视图控制器的运行时间比需要的时间长。出于这个原因,这似乎是一件愚蠢的事情。子视图控制器不需要强引用current,因为它毕竟是你的childViewControllers[0](如果正确执行子视图控制器“舞蹈”)。因此,您只是与您的财产一起复制childViewControllers属性已经所做的事情。

这样就把我们带到了第二个问题:你是在做一个好主意吗?不,我看到你来自哪里 - 你想为儿童视角控制器封装“舞蹈”。但无论如何你都在错误地跳舞;你因此颠覆了视图控制器的层次结构。为了封装“舞蹈”,我会说你做正确的舞蹈并提供执行它的函数以及引用的计算的只读属性要好得多。到childViewController[0]是否存在。

在这里,我假设我们一次只会有一个子视图控制器;我认为这比你想做的更好:

var current : UIViewController? {
    if self.childViewControllers.count > 0 {
        return self.childViewControllers[0]
    }
    return nil
}

func removeChild() {
    if let vc = self.current {
        vc.willMove(toParentViewController: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParentViewController()
    }
}

func createChild(_ vc:UIViewController) {
    self.removeChild() // There Can Be Only One
    self.addChildViewController(vc) // *
    // ... get vc's view into the interface ...
    vc.didMove(toParentViewController: self)
}

答案 1 :(得分:2)

我不认为使用didSet实际上是错误的。但是,最大的问题是您尝试在willSetdidSet之间拆分代码,因为根本不需要。您始终可以在oldValue中使用didSet

var current: UIViewController? = nil {
    didSet {
        guard current != oldValue else {
           return
        }

        oldValue?.willMove(toParentViewController: nil)            
        if let current = current {
            self.addChildViewController(current)
        }

        //... add current.view to the view hierarchy here...
        oldValue?.view.removeFromSuperview()

        oldValue?.removeFromParentViewController()
        current?.didMove(toParentViewController: self)
    }
}

顺便说一下,调用函数的顺序很重要。因此,我不建议将功能拆分为removeadd。否则,两个控制器的viewDidDisappearviewDidAppear的顺序可能会令人惊讶。