Должны ли мы всегда использовать [незанятое я] внутри закрытия в Свифт

В сеансе WWDC 2014 403 Промежуточный Swift и расшифровка, был следующий слайд

enter image description here

В этом случае говорящий сказал, что если мы не будем использовать [unowned self], это будет утечка памяти. Означает ли это, что мы всегда должны использовать [unowned self] внутри закрытия?

В строка 64 ViewController.swift приложения Swift Weather, я не использую [unowned self]. Но я обновляю пользовательский интерфейс, используя некоторые @IBOutlet, такие как self.temperature и self.loadingIndicator. Это может быть ОК, потому что все @IBOutlet я определены weak. Но для безопасности мы всегда должны использовать [unowned self]?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

Ответ 1

Нет, есть определенные моменты, когда вы не хотели бы использовать [unowned self]. Иногда вы хотите, чтобы замыкание захватывало себя, чтобы убедиться, что оно все еще вокруг к тому времени, когда вызывается замыкание.

Пример. Выполнение запроса асинхронной сети

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

Когда использовать unowned self или weak self

Единственный раз, когда вы действительно хотите использовать [unowned self] или [weak self], - это когда вы создадите сильный опорный цикл . Сильный ссылочный цикл - это когда существует замкнутый круг владения, где объекты в конечном итоге владеют друг другом (возможно, через стороннюю сторону), и поэтому они никогда не будут освобождены, потому что они оба гарантируют, что друг друга будут придерживаться.

В конкретном случае замыкания вам просто нужно понять, что любая переменная, на которую ссылаются внутри нее, получает "собственность" путем закрытия. Пока закрытие вокруг, эти объекты гарантированно будут вокруг. Единственный способ остановить это право собственности - сделать [unowned self] или [weak self]. Поэтому, если класс владеет закрытием, и это закрытие фиксирует сильную ссылку на этот класс, тогда у вас есть сильный ссылочный цикл между закрытием и классом. Это также включает в себя, если класс владеет тем, что владеет закрытием.

В частности, в примере из видео

В примере на слайде TempNotifier принадлежит закрытие через переменную-член onChange. Если бы они не объявляли self как unowned, замыкание также имело бы self, создавая сильный ссылочный цикл.

Разница между unowned и weak

Разница между unowned и weak заключается в том, что weak объявляется как необязательный, а unowned - нет. Объявив его weak, вы получите дело с тем, что в какой-то момент оно может быть в закрытии. Если вы попытаетесь получить доступ к переменной unowned, которая будет равна нулю, это приведет к сбою всей программы. Поэтому используйте unowned, когда вы уверены, что переменная всегда будет вокруг, а закрытие вокруг

Ответ 2

Обновление 11/2016

Я написал статью об этом расширении этого ответа (глядя на SIL, чтобы понять, что делает ARC), проверьте здесь.

Оригинальный ответ

Предыдущие ответы не дают простых правил о том, когда использовать друг друга и почему, поэтому позвольте мне добавить несколько вещей.

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

быстрый слабый против unowned

Сценарии

У вас может быть два возможных сценария:

  • Закрытие имеет одинаковое время жизни переменной, поэтому замыкание будет доступно только до тех пор, пока переменная не будет достижима. Переменная и замыкание имеют одинаковое время жизни. В этом случае вы должны объявить ссылку как unowned. Общим примером является [unowned self], используемый во многих примерах небольших замыканий, которые делают что-то в контексте их родителя и что нигде не упоминаются нигде, не переживайте своих родителей.

  • Время жизни замыкания не зависит от одной из переменных, на закрытие все равно можно ссылаться, когда переменная недоступна. В этом случае вы должны объявить ссылку как слабый и убедитесь, что она не равна нулю перед ее использованием (не принудительно разворачивать). Общим примером этого является [weak delegate], который вы можете увидеть в некоторых примерах замыкания, ссылающихся на полностью не связанный (пожизненный) делегирующий объект.

Фактическое использование

Итак, что вы/собираетесь использовать в большинстве случаев?

Цитата Джо Гроффа из twitter:

Unowned быстрее и допускает неизменность и неоптимальность.

Если вам не нужен слабый, не используйте его.

Вы найдете больше о неработающих * внутренних действиях здесь.

* Обычно также называемый незанятым (безопасным), чтобы указать, что проверки выполнения (которые приводят к сбою за недопустимые ссылки) выполняются до доступа к неопубликованной ссылке.

Ответ 3

Я думал, что добавлю некоторые конкретные примеры специально для контроллера вида. Многие объяснения, а не только здесь, о переполнении стека, действительно хороши, но я лучше работаю с примерами реального мира (на самом деле у @drewag было хорошее начало):

  • Если у вас есть закрытие для обработки ответа от сетевых запросов, используйте weak, потому что они долговечны. Контроллер вида мог закрыться до запрос заканчивается, поэтому self больше не указывает на действительный объект при вызове закрытия.
  • Если у вас есть закрытие, которое обрабатывает событие на кнопке. Это может быть unowned, потому что как только контроллер просмотра уйдет, кнопка и любые другие элементы, которые она может ссылаться на self, уходят одновременно. Блок блокировки также уйдет в одно и то же время.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }
    

Ответ 4

Если self может быть nil в закрытии, используйте [слабый я].

Если self никогда не будет равным нулю в использовании закрытия, используйте [unowned self].

Документация Apple Swift имеет отличный раздел с изображениями, объясняющими разницу между использованием Сильной, Слабый и unowned в закрытии:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

Ответ 5

Вот блестящие цитаты из Форумы разработчиков Apple описывали вкусные детали:

unowned vs unowned (safe) vs unowned (небезопасно)

unowned (safe) - это ссылка, не относящаяся к владельцам, которая утверждает, что объект все еще жив. Это похоже на слабую дополнительную ссылку это неявно разворачивается с помощью x! при каждом доступе. unowned (небезопасно) похож на __ unsafe_unretained в ARC - это не владеющий ссылку, но нет проверки времени выполнения, что объект все еще жив на доступ, поэтому болтающиеся ссылки попадут в память мусора. unowned всегда является синонимом unowned (safe) в настоящее время, но что он будет оптимизирован для unowned (небезопасно) в -Ofastстроит, когда проверки времени выполнения отключены.

unowned vs слабый

unowned фактически использует гораздо более простую реализацию, чем weak. Объекты Native Swift несут два подсчета ссылок и unownedссылки ссылаются на unowned количество ссылок вместо strongсчетчик ссылок. Объект деинициализируется, когда ссылка strongcount достигает нуля, но фактически не освобождается до тех пор, пока unowned количество ссылок также попадает в ноль. Это приводит к тому, что память удерживаются на немного дольше, когда имеются неопубликованные ссылки, но это обычно не является проблемой при использовании unowned, потому что связанный в любом случае объекты должны иметь почти равные сроки жизни, и это намного проще и более низкими накладными расходами, чем реализация на основе табличного стола, используемая для обнуление слабых ссылок.

Обновление: В современном Swift weak внутренне используется тот же механизм, что и unowned. Поэтому это сравнение неверно, поскольку оно сравнивает Objective-C слабый с Swift unonwed.

Причины

В чем заключается цель сохранения памяти после владения ссылками до 0? Что произойдет, если код попытается сделать что-то с объект с использованием неосновной ссылки после деинициализации?

память сохраняется в памяти, так что ее учетные данные остаются доступными. Таким образом, когда кто-то пытается сохранить сильную ссылку на unowned object, среда выполнения может проверить, что сильный счетчик ссылок больше нуля, чтобы обеспечить сохранность объект.

Что происходит с владением или отсутствием ссылок, хранящихся объектом? Является ли их время жизни отделенным от объекта, когда оно деинициализировано или их память также сохраняется до тех пор, пока объект не будет освобожден после последняя освобожденная ссылка освобождена?

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

Возбужденный, да?

Ответ 6

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

Но Майк Эш задокументировал, как Swift обновил реализацию слабых vars для использования боковых столов и как это существенно улучшает слабую самоэффективность.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

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

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

Ответ 7

Согласно Apple-doc

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

  • Если захваченная ссылка никогда не станет нулевой, она всегда должна быть записана как неосновная ссылка, а не слабая ссылка

Пример -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }

Ответ 8

Сильные справочные циклы для замыканий

Цикл строгой ссылки также может произойти, если вы назначаете замыкание свойству экземпляра класса, а тело этого замыкания захватывает экземпляр. Этот захват может происходить из-за того, что тело замыкания обращается к свойству экземпляра, например self.someProperty, или потому что замыкание вызывает метод экземпляра, например self.someMethod(). В любом случае, эти обращения заставляют замыкание "захватывать" self, создавая сильный контрольный цикл. Для получения дополнительной информации о захвате значений в замыкании

Swift предоставляет элегантное решение этой проблемы, известное как список захвата закрытия. Список захвата определяет правила, используемые при захвате одного или нескольких ссылочных типов в теле замыканий. Как и в случае циклов сильных ссылок между двумя экземплярами классов, вы объявляете каждую захваченную ссылку weak или unowned ссылкой, а не strong ссылкой. Соответствующий выбор weak или unowned зависит от отношений между различными частями вашего кода. больше здесь ТАК

  • Используйте weak ссылку, когда она действительна, чтобы в какой-то момент времени эта ссылка стала nil.
  • Используйте unowned ссылку, когда вы знаете, что ссылка не будет nil, как только он был установлен во время инициализации.

Док с примером

Ответ 9

Если ничего из вышеперечисленного не имеет смысла:

ТЛ; др

Точно так же, как implicitly unwrapped optional, если вы можете гарантировать, что ссылка не будет нулевой в своей точке использования, используйте unowned. Если нет, то вы должны использовать слабый.

Объяснение:

Ниже я нашел следующее: слабая ссылка без ссылки. Исходя из того, что я понял, непризнанное "я" не может быть нулевым, но слабое "я" может быть, а непризнанное "я" может привести к висящим указателям... что-то позорное в Objective-C. Надеюсь, поможет

"НЕИЗВЕСТНЫЕ и слабые ссылки ведут себя одинаково, но НЕ одинаковы".

Неизвестные ссылки, такие как слабые ссылки, не увеличивают количество сохраняемых объектов, на которые ссылаются. Тем не менее, в Swift, неподтвержденная ссылка имеет дополнительное преимущество - она не является дополнительной. Это облегчает управление ими, а не использование дополнительной привязки. Это мало чем отличается от неявно развернутых опций. Кроме того, неизвестные ссылки не обнуляются. Это означает, что когда объект освобождается, он не обнуляет указатель. Это означает, что использование неизвестных ссылок в некоторых случаях может привести к появлению висящих указателей. Для вас, ботаников, которые помнят дни Objective-C, как и я, неподтвержденные ссылки отображаются на unsafe_unretained.

Это где это немного сбивает с толку.

Слабые и неподтвержденные ссылки не увеличивают количество сохраняемых данных.

Они оба могут быть использованы для прерывания сохранения циклов. Так когда же мы их используем?!

Согласно Apple, документы:

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