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

На сессии WWDC 2014 403 Intermediate Swift и стенограмма был следующий слайд

Спикер сказал в этом случае, если мы не используем [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
    }
  }
}

12 ответов

Решение

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

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

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

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

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

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

https://www.uraimo.com/imgs/unownedbig.png

Сценарии

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

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

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

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

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

Цитирую Джо Гроффа из твиттера:

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

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

Вы найдете больше информации о незнакомых* внутренняя работа здесь.

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

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

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

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

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

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

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

unowned против unowned(safe) против unowned(unsafe)

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

unowned против weak

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

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

Причины

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

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

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

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

В восторге?

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

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

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

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

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

По словам Apple-док

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

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

Пример -

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

неуправляемый похож на слабый, они не сохраняют объект от уничтожения, но слабые переменные превращаются в nil, когда объект, на который он ссылается, больше не существует, что мы можем обработать с помощью обычной проверки nils, неуправляемые просто станут мусором, вы не можете сказать, что они больше не мусор, и их использование приведет к сбою. Проблема со слабыми заключается в том, что если объект имеет ссылки на него слабыми переменными, когда он уничтожается, он должен просмотреть каждую ссылку на него и установить для этой переменной значение nil, это явно будет дорого, вместо этого использование unowned будет просто сбой, и найти такую ​​​​ошибку будет сложно. Одно из мест, где можно использовать unowned, — это если вы создаете какой-то тщательно хранимый тип данных, который имеет понятный интерфейс, а его внутренние компоненты недоступны напрямую.

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

ТЛ; др

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

Объяснение:

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

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

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

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

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

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

Согласно документации Apple:

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

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

Если вы не уверены в [unowned self] тогда используйте [weak self]

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

Что происходит с другими несильными ссылками? Очевидно, что они больше не ссылаются на этот объект, что проблематично. Есть два способа справиться с этим:

  1. Слабая ссылка. Когда последняя сильная ссылка на объект исчезает, все слабые ссылки обнуляются, поэтому разработчик может проверить, существует ли больше объект, на который ссылаются. Совершенно очевидно, что слабая ссылка должна быть необязательной, иначе она не может быть установлена ​​в nil. Стратегия использования слабой ссылки: вы пишете «if let ref = weakref». Либо ссылка все же была, а так как вы только что присвоили ей сильную ссылку, то она останется до конца «если позволит». Если вы не сделаете этого таким образом, вы можете дважды получить доступ к одной и той же слабой ссылке, и она может быть (неожиданно) не нулевой при первом доступе, а нулевой при втором.

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

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

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

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

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

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

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

Другие вопросы по тегам