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

Strongly referenced variable may cause memory issues

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


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

这个在上面:

In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.

在下一段中,如下:

To make this possible, whenever you assign a class instance to a property, constant, or variable, that property, constant, or variable makes a strong reference to the instance. The reference is called a “strong“ reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.


我对情况的动态有点困惑。我在使用情节提要时注意到,您将引用设置为弱,因此 class 看起来像这样,也就是我所说的情况 1:

案例一

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

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

这里,标签与 ViewController 具有一对一的弱引用,一旦更改控制器,引用就会被破坏(内存释放),因为它是弱引用。因此,没有与内存相关的问题。

如果上述陈述有误或不严谨,请见谅。如果有人证实或反驳我的假设,我会很高兴。


我的问题是关于第二种情况,我不使用情节提要,class 如下所示:

案例二

class SomeClass : UIViewController {
    var nameLabel : UILabel = {

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

    }()

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

对于上述情况,我的假设是 ViewController 与标签具有一对一的强引用,并且 ViewController 内部的视图也与标签具有强引用..如果 class 被更改/标签从子视图中删除..那么我认为内存不会被释放。或者至少视图控制器将保持对标签的强引用(根据文档。)

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

我唯一能从中收集到的是,虽然标签已从 UIView 中删除,但它仍然与控制器保持强引用,因此在内存中具有永久状态。我说得对吗?

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

    weak var nameLabel : UILabel = {

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

    }()

如果像第二种情况那样声明变量会导致永久性强引用,我应该如何声明它们而不是内存问题?


总而言之,我的问题是:

在没有使用故事板出口的情况下,变量被强烈引用到视图控制器,这些引用会导致内存问题吗?

如果是,我必须遵循什么code declaration practice

如果不是这样,请提供深思熟虑的论据和有效的解释来反驳它。


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

提前致谢。

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

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

我认为视图控制器的强引用变量不会导致任何内存问题。

通常视图在解除分配它们的视图控制器之前被解除分配。例如,在您的代码中,当取消分配视图时,ARC 减少指向名称标签的计数器,因此它从 2 传递到 1。然后,当取消分配视图控制器时,它再次减少计数器,从 1 到 0。一旦有0 个指向已删除名称标签的引用。

A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance. This behavior prevents the reference from becoming part of a strong reference cycle. You indicate a weak reference by placing the weak keyword before a property or variable declaration

> Weak references must be declared as variables, to indicate that their value can change at runtime. A weak reference cannot be declared as a constant.

Because a weak reference does not keep a strong hold on the instance it refers to, it is possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. Because weak references need to allow nil as their value, they always have an optional type. You can check for the existence of a value in the weak reference, just like any other optional value, and you will never end up with a reference to an invalid instance that no longer exists

来源: Apple docs

弱引用只是一个指向对象的指针,它不能保护对象不被 ARC 释放。强引用将对象的保留计数增加 1,而弱引用则不会。此外,当对象成功释放时,弱引用会将指向对象的指针清零。这确保当你访问一个弱引用时,它要么是一个有效的对象,要么是 nil。

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

The only thing I could gather from this was that although the label was removed from UIView, it still maintained a strong reference with the controller, hence permanent state in memory. Am I right?

没有。这里没有什么大问题。

标签没有对视图控制器的强引用——如果有,将是一个保留循环,并会导致标签和视图控制器泄漏。正是出于这个原因,视图应该永远不会保持对其视图控制器的强引用。

然而,这里的情况正好相反:视图控制器对标签有很强的引用。没关系。确实,标签在从其父视图中删除后仍然存在。但这可能还不错。在很多情况下,这很好!例如,假设您打算稍后将标签放回界面;您将需要保留它。

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

但无论如何这里没有泄漏,您应该停止担心。当视图控制器不存在时,标签也将不存在。标签的寿命比它必须的要长,但从大范围来看,这微不足道且不重要。

我总是这样给我的学生解释。

有了强引用,您可以看到一个值,并且在它周围有一个套索。您对价值是否仍然存在有发言权。

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

针对你的情况,暂时避免内存泄漏的发生。你可以选择马特的回答。

为了更好地理解,在构建阶段->编译源中的 MRC 标志下创建自定义 UILabel class。

在自定义 class 中,覆盖保留和释放方法。给它们设置断点。

在您的视图控制器中使用该自定义 UILabel class,并打开 ARC 标志。使用 matt answer 或使用以下可选的 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 时导致崩溃。