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

467

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

введите описание изображения здесь

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

В строке 64 файла ViewController.swift приложения Swift Weather я не пользуюсь [unowned self]. Но я обновляю пользовательский интерфейс, используя некоторые @IBOutlets 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
    }
  }
}
Джейк Лин
источник
ссылка на изображение не работает
Даниэль Гомес Рико
@ DanielG.R. Спасибо, я вижу это. i.stack.imgur.com/Jd9Co.png
Джейк Лин
2
Если я не ошибаюсь, приведенный на слайде пример неверен - onChangeдолжен быть [weak self]закрытием, так как это открытое (внутренне, но все же) свойство, поэтому другой объект может получить и сохранить закрытие, сохраняя объект TempNotifier вокруг (неопределенно, если использующий объект не отпустил onChangeзамыкание, пока не увидит, что TempNotifierоно ушло, через свою собственную слабую ссылку на TempNotifier) . Если бы var onChange …было private var onChange …то [unowned self]было бы правильно. Я не уверен на 100% в этом; кто-нибудь поправьте меня, пожалуйста, если я ошибаюсь.
Слипп Д. Томпсон
@ Джейк Лин `var onChange: (Int) -> Void = {}` представляют ли фигурные скобки пустое замыкание? так же, как в определении пустого массива с []? Я не могу найти объяснение в документации Apple.
bibscy
@bibscy yes, {}является пустым замыканием (экземпляром замыкания) по умолчанию (ничего не делает), (Int) -> Voidявляется определением замыкания.
Джейк Лин,

Ответы:

871

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

drewag
источник
1
Привет. Отличный ответ. Я изо всех сил пытаюсь понять непосвященного себя. Мне не достаточно причины использовать слабый самоочевидный просто «я становится необязательным». Почему я специально хочу использовать 'unowned self' stackoverflow.com/questions/32936264/…
19
@robdashnash, преимущество использования unowned self состоит в том, что вам не нужно разворачивать необязательный код, который может быть ненужным кодом, если вы точно знаете, что он никогда не будет равен нулю. В конечном счете, unowned self используется для краткости и, возможно, также как подсказка будущим разработчикам, что вы никогда не ожидаете нулевого значения.
drewag
77
Случай для использования [weak self]в асинхронном сетевом запросе находится в контроллере представления, где этот запрос используется для заполнения представления. Если пользователь отступает, нам больше не нужно заполнять представление, а также нам не нужна ссылка на контроллер представления.
Дэвид Джеймс
1
weakссылки также устанавливаются, nilкогда объект освобождается. unownedссылки не являются.
BergQuester
1
Я немного смущен. unownedиспользуются для некоторого non-Optionalвремени weakиспользуются для Optionalтак нашего selfэто Optionalили non-optional?
Мухаммед Наяб
193

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

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

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

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

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

быстрые слабые против незнакомых

Сценарии

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

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

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

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

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

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

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

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

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

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

Умберто Раймонди
источник
26
Я устал слышать объяснение попугая "используйте неделю, если self может быть нулевым, используйте unowned, когда оно никогда не может быть нулевым". Хорошо, мы получили это - слышали это миллион раз! Этот ответ на самом деле гораздо глубже, когда «я» может быть нулевым на простом английском, что напрямую отвечает на вопрос ОП. Спасибо за это отличное объяснение !!
TruMan1
Спасибо @ TruMan1, на самом деле я пишу пост на эту тему, который скоро появится в моем блоге, обновлю ответ ссылкой.
Умберто Раймонди
1
Хороший ответ, очень практичный. Я вдохновлен, чтобы переключить некоторые из моих слабых переменных, чувствительных к производительности, на неизвестные.
original_username
«Время жизни замыкания не зависит от переменной». У вас есть опечатка?
Honey
1
Если замыкание всегда имеет то же время жизни, что и родительский объект, не будет ли учитываться счетчик ссылок, когда объект уничтожается? Почему вы не можете просто использовать «я» в этой ситуации вместо того, чтобы беспокоиться о незнакомых или слабых?
LegendLength
105

Я думал, что добавлю несколько конкретных примеров специально для контроллера представления. Многие из объяснений, не только здесь, о переполнении стека, действительно хороши, но я лучше работаю с примерами из реального мира (@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.
     }
possen
источник
17
Это нужно больше голосов. Два убедительных примера, показывающих, как замыкание при нажатии кнопки не будет существовать вне срока службы контроллера представления, и, следовательно, могут использовать неизвестные, но большинство сетевых вызовов, которые обновляют пользовательский интерфейс, должны быть слабыми.
Тим Фукуа
2
Итак, просто чтобы уточнить, всегда ли мы используем не владеющих или слабых при вызове self в блоке замыкания? Или есть время, когда мы не будем называть слабых / не владеющих? Если да, не могли бы вы привести пример?
Лука
Огромное спасибо.
Шон Бэк
1
Это дало мне более глубокое понимание о [слабом я] и [непризнанном я] Большое спасибо @possen!
Томми
Это замечательно. Что делать, если у меня есть анимация, которая основана на взаимодействии с пользователем, но требует времени, чтобы закончить. И тогда пользователь переходит к другому viewController. Я полагаю, в таком случае я все еще должен использовать, weakа не unownedправильно?
Мед
67

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

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

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

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

TenaciousJay
источник
50

Вот блестящие цитаты из форумов разработчиков 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со Swift unonwed.

Причины

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

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

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

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

В восторге?

Валентин Шергин
источник
38

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

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

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

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

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

SafeFastExpressive
источник
Спасибо за обновление и аминь в последнем абзаце.
девиз
1
Итак, после новых изменений Есть ли время, когда weakнельзя использовать вместо unowned?
Мед
4

По словам 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)
     }
Джек
источник
0

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

ТЛ; др

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

Объяснение:

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

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

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

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

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

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

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

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

Дакш Гаргас
источник
0
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]

Шуроб Датта
источник