Будем ли мы всегда использовать [unowned self] внутри закрытия в Swift

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

enter image description here

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

On строка 64 ViewController.Свифт на Свифт погоды, я не использую [unowned self]. Но я обновляю UI, используя некоторые @IBOutletкак self.temperature и self.loadingIndicator. Это может быть нормально, потому что все @IBOutlets я определилweak. Но для безопасности, мы должны всегда использовать [unowned self]?

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

7 ответов


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

пример: создание асинхронного сетевого запроса

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

при использовании unowned self или weak self

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

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

конкретно в примере из видео

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

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

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


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

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

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

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

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

swift weak vs unowned

сценарии

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

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

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

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

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

цитирование Джо Гроффа из twitter:

Unowned быстрее и позволяет для неизменяемости и nonoptionality.

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

вы найдете больше о unowned* внутреннее устройство здесь.

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


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

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

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

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


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

unowned vs unowned(safe) vs unowned(unsafe)

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

unowned vs weak

unowned на самом деле использует гораздо более простую реализацию, чем weak. Собственные объекты Swift несут два отсчета ссылок и unowned ссылки на литературу bump unowned счетчик ссылок вместо сильный счетчик ссылок. Объект deinitialized, когда его сильный ссылка граф!--38--> достигает нуля, но на самом деле не освобождается до unowned счетчик ссылок также падает до нуля. Это заставляет память быть держался немного дольше, когда есть неиспользуемые ссылки, но это не обычно проблема, когда unowned используется потому, что соответствующие в любом случае объекты должны иметь почти равное время жизни, и это намного проще и более низкие накладные расходы, чем реализация на основе боковой таблицы, используемая для обнуление слабых ссылок.

обновление: в современном Swift weak внутренне использует тот же механизм, что unowned тут. Таким образом, это сравнение неверно, потому что оно сравнивает Objective-C weak С Swift unonwed.

причины

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

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

что происходит с владением или непризнанными ссылками, удерживаемыми объектом? Их жизни зависят от объекта, когда он deinitialized или их память также сохраняется до тех пор, пока объект не будет освобожден после освобождена последняя неучтенная ссылка?

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

волноваться, да?


Я думал, что добавлю некоторые конкретные примеры специально для контроллера вида. Многие из объяснений, а не только здесь, в Stack Overflow, действительно хороши, но я лучше работаю с примерами реального мира (@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.
     }
    

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

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

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

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

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


по данным 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)
     }