Сильная ссылочная переменная может вызвать проблемы с памятью

Я программировал в Swift пару месяцев. В последнее время я больше сосредоточился на концепциях того, как Swift работает как язык.


Следовательно, недавно, читая документацию Apple по автоматической подсчету ссылок (ARC), я наткнулся на следующие строки:

Этот наверху:

В большинстве случаев это означает, что управление памятью "просто работает" в Swift, и вам не нужно думать об управлении памятью самостоятельно. ARC автоматически освобождает память, используемую экземплярами класса, когда эти экземпляры больше не нужны.

И в следующем абзаце следующее:

Чтобы сделать это возможным, всякий раз, когда вы присваиваете экземпляр класса свойству, константе или переменной, это свойство, константа или переменная делает сильную ссылку на экземпляр. Ссылка называется "сильной" ссылкой, потому что она держится на этом экземпляре и не позволяет ее освобождать до тех пор, пока эта сильная ссылка остается.


Я немного смущен, что такое динамика ситуации. Я заметил при использовании раскадровки, что вы установили ссылку на слабый, следовательно, класс выглядит так, и то, что я бы назвал case 1:

Случай 1

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

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

Здесь метка имеет один-к-одному слабую ссылку с ViewController, и как только контроллер изменен, ссылка сломана (память 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 должен я следовать?

Если это не так, предоставьте вдумчивые аргументы с действительными объяснениями, чтобы противостоять им.


Опять же, простите меня, если я где-то ошибаюсь.

Спасибо заранее.

Ответы

Ответ 1

Единственное, что я мог извлечь из этого, было то, что, хотя метка была удалена из UIView, она по-прежнему поддерживала сильную ссылку на контроллер, следовательно, постоянное состояние в памяти. Я прав?

Нет. Здесь нет большой проблемы.

Ярлык не имеет сильной ссылки на контроллер представления - если бы это было так, это было бы циклом удержания и вызовет утечку как метки, так и контроллера представления. По этой причине представление никогда не должно содержать ссылки на его контроллер.

Здесь, однако, это наоборот: контроллер представления имеет сильную ссылку на метку. Это здорово. Правда, этикетка, следовательно, остается в наличии после того, как она была удалена из своего супервизора. Но это может быть не так. Во многих случаях это хорошо! Например, предположим, что вы намерены снова поместить этикетку в интерфейс; вам нужно будет сохранить его.

Если вы уверены, что вам не нужно будет хранить ярлык позже, просто используйте необязательную упаковку UILabel в качестве свойства вашего экземпляра. Таким образом, вы можете назначить nil свойству экземпляра метки, когда вы закончите с ним, и метка исчезнет.

Но в любом случае здесь нет утечки, и вы должны просто перестать беспокоиться. Когда контроллер просмотра перестанет существовать, метка также исчезнет. Ярлык жил дольше, чем нужно, но был крошечным и несущественным по большому счету вещей.

Ответ 2

создайте 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 тоже будет выпущен.

Demo

Я написал легкую демонстрацию, чтобы 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)
}
}

Ответ 3

Я не думаю, что сильно связанные переменные для просмотра контроллера вызывают проблемы с памятью.

Обычно просмотры освобождаются до освобождения их контроллера вида. Например, в вашем коде при отключении представления ARC уменьшает счетчик, указывающий на namelabel, поэтому он переходит от 2 к 1. Затем при освобождении контроллера просмотра он снова уменьшает счетчик, от 1 до 0. Когда есть 0 ссылок, указывающих на удаление его.

Ответ 4

Слабая ссылка - это ссылка, согласно которой не удерживает экземпляр, который он ссылается на, и поэтому не останавливает ARC от утилизации ссылочный экземпляр. Такое поведение предотвращает становясь частью сильного эталонного цикла. Вы указываете на слабый ссылку, поместив слабые ключевые слова перед свойством или переменной Объявление

> Слабые ссылки должны быть объявлены как переменные, чтобы указать, что их значение может меняться во время выполнения. Слабая ссылка не может быть объявлена ​​как константа.

Поскольку слабая ссылка не удерживает сильную фиксацию экземпляра это означает, что возможно, чтобы этот экземпляр был освобожден слабая ссылка по-прежнему относится к ней. Поэтому ARC автоматически устанавливает слабую ссылку на nil, когда экземпляр, который он означает освобождение. Поскольку слабые ссылки должны позволять nil как их значение, они всегда имеют необязательный тип. Вы можете проверить существование значения в слабой ссылке, как и любое другое необязательное значение, и вы никогда не получите ссылку на недопустимый экземпляр, который больше не существует

Источник: Apple docs

Слабая ссылка - это просто указатель на объект, который не защищает объект от освобождения от ARC. В то время как сильные ссылки увеличивают количество удержаний объекта на 1, слабых ссылок нет. Кроме того, слабые ссылки обнуляют указатель на ваш объект, когда он успешно освобождается. Это гарантирует, что при доступе к слабой ссылке он будет либо действительным объектом, либо нулем.

Надежда может помочь вам лучше понять слабую ссылку, будь то связанная с элементом раскадровки или созданная программно.

Ответ 5

Я всегда объясняю это своим ученикам следующим образом.

С сильной ссылкой вы можете увидеть значение, и вокруг него есть лассо. У вас есть мнение о том, осталось ли значение.

При слабой ссылке вы можете это увидеть, но там нет лассо. Вы не можете сказать, живет ли это значение.

Ответ 6

Для вашей ситуации, чтобы избежать возникновения утечки памяти на секунду. Вы можете пойти с Мэттом.

Для лучшего понимания создайте пользовательский класс UILabel под флагом MRC в фазах сборки- > Источники Complie.

В пользовательском классе переопределите метод сохранения и выпуска. Поместите точки останова на них.

Используйте этот пользовательский класс UILabel в контроллере просмотра с включенным флагом ARC. Перейдите с матовым ответом или используйте ниже необязательное объявление 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.