强引用变量可能导致内存问题

时间:2016-07-13 08:11:43

标签: ios swift memory-management automatic-ref-counting

我已经在Swift编程了几个月了。最近,我更多地关注Swift作为一种语言如何运作的概念。

因此,最近在阅读apple documentation on Automatic Reference Counting(ARC)时,我遇到了以下几行:

这一个在上面:

  

在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理。当不再需要这些实例时,ARC会自动释放类实例使用的内存。

在下一段中,以下内容:

  

为了实现这一点,无论何时将类实例分配给属性,常量或变量,该属性,常量或变量都会对实例进行强引用。该引用被称为“强”引用,因为它保持对该实例的坚定保留,并且只要该强引用仍然存在就不允许它被释放。

我对这种情况的动态有点困惑。我在使用故事板时注意到,你将引用设置为弱,因此类看起来像这样,我也称之为案例1:

案例1

class SomeClass : UIViewController {
    @IBOutlet weak var nameLabel : UILabel!

    override func viewDidLoad() {
        nameLabel.text = "something."  
    }  
}

这里,标签与ViewController有一对一的弱引用,一旦Controller被更改,引用就会被破坏(内存dealloc),因为它很弱。因此,没有与记忆有关的问题。

如果上述陈述错误或松散,请原谅我。如果有人确认我的假设或反击它,我会很高兴。

我的问题是关于第二种情况,但我不使用故事板和类如下所示:

案例2

class SomeClass : UIViewController {
    var nameLabel : UILabel = {

      let label = UILabel()
      label.translatesAutoresizingMaskIntoConstraints = false
      return label

    }()

    override func viewDidLoad() {
        view.addSubView(nameLabel)
        // view.addConstraints...
    }  
}

对于上面的情况,我的假设是ViewController与标签有一对一的强引用,ViewController中的视图也有标签的强引用..如果类被更改/标签被删除subview ..然后我认为内存不会被释放。或者至少视图控制器将保持对标签的强引用(根据文档。)

我通过从视图的子视图中删除标签并打印出标签来确认这一点(它给了我一个UILabel的实例,其框架位于0原点和0大小。)因此一个实例不是零。

我唯一可以从中收集的是,尽管标签已从UIView中删除,但它仍然保持与控制器的强引用,因此在内存中保持永久状态。我是对的吗?

如果是这种情况。我应该如何防止我的代码出现此类内存问题?更大的问题是,如果我像这样声明我的变量,那么在将它添加为控制器中主视图的子视图时,我会得到一个零。

    weak var nameLabel : UILabel = {

      let label = UILabel()
      label.translatesAutoresizingMaskIntoConstraints = false
      return label

    }()

如果在第二种情况下声明变量会导致永久强引用,那么我应该如何声明这些变量而不会出现内存问题?

总而言之,我的问题是:

如果没有使用故事板出口,并且变量被视图控制器强烈引用,这些引用是否会导致内存问题?

如果是这样,我必须遵循code declaration practice

如果不是这样,请提供深思熟虑的论据以及有效的解释来解决这个问题。

再次,如果我在任何地方不正确,请原谅我。

提前谢谢。

6 个答案:

答案 0 :(得分:11)

  

我唯一可以从中收集的是,尽管标签已从UIView中删除,但它仍然保持与控制器的强引用,因此在内存中保持永久状态。我是对的吗?

没有。这里没有大问题。

标签没有对视图控制器的强引用 - 如果是, 将是保留周期,并且会导致标签和视图控制器泄漏。出于这个原因,视图永远不会保持对其视图控制器的强引用。

然而,在这里,反过来说:视图控制器对标签有强烈的引用。没关系。确切地说,标签在从超级视图中删除后仍然存在。但那可能并不坏。在许多情况下,它很好!例如,假设您打算稍后将标签放回界面;你将需要保留它。

如果您确定以后不需要保留标签,那么只需使用Optional包装UILabel作为您的实例属性即可。这样,您可以在完成标签实例属性后将nil分配给标签实例属性,标签将不再存在。

但无论如何,没有泄漏在这里,你应该停止担心。当视图控制器不存在时,标签也将不存在。这个标签的寿命比以往任何时候都要长,但对于大规模的事情来说,这个标签很小而且不重要。

答案 1 :(得分:1)

在您需要时创建label,然后致电addsubView对其进行强引用,并对您的成员var进行弱引用,如下所示:

class ViewController: UIViewController {

weak var label : UILabel?

override func viewDidLoad() {
    super.viewDidLoad()

    let label = UILabel()
    view.addSubview(label)
    self.label = label

}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

    print(label)
    //click first Optional(<UILabel: 0x7fb562c3f260; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb562c11c70>>)
    //click second nil
    label?.removeFromSuperview()
}
}

viewcontroller版本发布时,标签将会发布,view.subview也会发布。

演示

我写了一个简单的演示,让ViewControllerTest成为rootviewcontroller

class Test{

weak var label:UILabel?

static let instance = Test()


}



class ViewControllerTest: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
    self.navigationItem.rightBarButtonItem = item

}

func test(){
    print(Test.instance.label)
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {


    let vc = ViewController()
    self.navigationController?.pushViewController(vc, animated: true)
    print(vc.nameLabel)
    let test = Test.instance
    test.label = vc.nameLabel

}

}



class ViewController: UIViewController {

var nameLabel : UILabel = {

    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    return label

}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = UIColor.whiteColor()
    view.addSubview(nameLabel)

    let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
    self.navigationItem.rightBarButtonItem = item

}

func test(){
    print(Test.instance.label)
}
}

答案 2 :(得分:1)

我不认为强烈引用变量来查看控制器会导致任何内存问题。

通常在取消分配视图控制器之前会取消分配视图。例如,在你的代码中,当取消分配视图时,ARC减少指向namelabel的计数器,因此它从2传递到1.然后,当取消分配视图控制器时,它再次减少计数器,从1到0。 0个引用指向namelabel的删除。

答案 3 :(得分:1)

  

弱参考是没有强力保留的参考   它引用的实例,因此不会阻止ARC处置   引用的实例。此行为会阻止引用   成为强大的参考周期的一部分。你表示弱势   通过在属性或变量之前放置weak关键字来引用   声明

     

&GT;必须将弱引用声明为变量,以指示它们的变量   值可以在运行时更改。弱引用不能声明为a   恒定。

     

因为弱引用不能保持对实例的强烈保持   它指的是,该实例可以在释放时释放   弱引用仍指它。因此, ARC   当实例表示它时,自动设置一个弱引用为nil   是指解除分配。因为弱引用需要允许nil为   他们的价值,他们总是有一个可选的类型。你可以检查一下   弱参考中存在一个值,就像任何其他参数一样   可选值,你永远不会得到一个引用   无效的实例不再存在

来源Apple docs

弱引用只是一个指向对象的指针,该对象不保护对象不被ARC释放。虽然强引用会将对象的保留计数增加1,但弱引用则不会。此外,弱引用在成功解除分配时将指针归零。这可以确保在访问弱引用时,它将是有效对象,或者为nil。

希望可以帮助您更好地理解弱引用,无论是与故事板项目相关还是以编程方式创建。

答案 4 :(得分:1)

我总是这样向学生解释。

通过强大的参考,您可以看到一个值,并且您有一个围绕它的套索。你对价值是否仍然存在有发言权。

弱引用,你可以看到它,但没有套索。你对价值是否存在没有发言权。

答案 5 :(得分:1)

为您的情况避免发生内存泄漏一秒钟。你可以跟Matt一起回答。

为了更好地理解,请在构建阶段中使用MRC标记创建自定义UILabel类 - &gt; Complie sources。

在自定义类中,覆盖retain和release方法。在它们上面加点断点。

在视图控制器中使用该自定义UILabel类,并将ARC标志设置为ON。使用哑光答案或使用UILabel的可选声明。

import UIKit

class ViewController: UIViewController {
    var label:UILabel? = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "something"
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(self.label!)
        //namelabel goes out of scope when method exists.
        //self.view has 1+ ref of self.label
    }
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.label?.removeFromSuperview()//-1 ref of self.label
        self.label = nil
        print(self.label)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

您将清楚地了解ARC如何工作以及为什么UILabel的弱引用会在添加到UIView时导致崩溃。