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

340

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

Изображение 53

В этом случае говорящий сказал, что если мы не будем использовать [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
    }
  }
}
  • 0
    ссылка на изображение не работает
  • 0
    @ DanielG.R. Спасибо, я вижу это. i.stack.imgur.com/Jd9Co.png
Показать ещё 3 комментария
Теги:
automatic-ref-counting

7 ответов

672
Лучший ответ

Нет, есть определенные моменты, когда вы не хотели бы использовать [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, когда вы уверены, что переменная всегда будет вокруг, а закрытие вокруг

  • 0
    Чтобы прояснить, и в связи с этим примером, TempNotifier и замыкание будут владеть self без unowned или weak ключевых слов?
  • 1
    Привет. Отличный ответ. Я изо всех сил пытаюсь понять непосвященного себя. Мне не достаточно причины использовать слабый самообладание просто потому, что «я становлюсь необязательным». Почему я специально хочу использовать «unowned self» stackoverflow.com/questions/32936264/…
Показать ещё 6 комментариев
129

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

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

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

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

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

Изображение 1645

Сценарии

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

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

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

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

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

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

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

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

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

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

  • 16
    Я устал слышать объяснение попугая "используйте неделю, если self может быть нулем, используйте unowned, когда оно никогда не может быть нулем". Хорошо, мы получили это - слышали это миллион раз! Этот ответ на самом деле гораздо глубже, когда «я» может быть нулевым на простом английском языке, что напрямую отвечает на вопрос ОП. Спасибо за это отличное объяснение !!
  • 0
    Спасибо @ TruMan1, на самом деле я пишу пост об этом, который скоро появится в моем блоге, обновлю ответ ссылкой.
Показать ещё 6 комментариев
51

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

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

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

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

38

Вот блестящие цитаты из Форумы разработчиков 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, среда выполнения может проверить, что сильный счетчик ссылок больше нуля, чтобы обеспечить сохранность объект.

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

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

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

21

Я думал, что добавлю некоторые конкретные примеры специально для контроллера вида. Многие объяснения, а не только здесь, о переполнении стека, действительно хороши, но я лучше работаю с примерами реального мира (на самом деле у @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.
     }
    
  • 10
    Это нужно больше голосов. Два убедительных примера, показывающих, как замыкание при нажатии кнопки не будет существовать за пределами срока службы контроллера представления, и, следовательно, могут использовать неизвестные, но большинство сетевых вызовов, которые обновляют пользовательский интерфейс, должны быть слабыми.
  • 2
    Итак, просто чтобы уточнить, всегда ли мы используем не владеющих или слабых при вызове self в блоке замыкания? Или есть время, когда мы не будем называть слабых / не владеющих? Если да, не могли бы вы привести пример?
Показать ещё 8 комментариев
10

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

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

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

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

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

  • 0
    Спасибо за обновление и аминь по последнему пункту.
  • 1
    Итак, после новых изменений Есть ли когда-нибудь время, когда weak не может быть использовано вместо unowned ?
2

Согласно 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)
     }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню