Чи завжди ми будемо використовувати [невідоме “у закритті у 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%; хтось мене виправить, будь ласка, якщо я не прав
Сліп Д. Томпсон

@Jake Lin `var onChange: (Int) -> void = {}` чи фігурні дужки являють собою порожнє закриття? те саме, що і при визначенні порожнього масиву з []? Я не можу знайти пояснення в документах Apple.
bibscy

@bibscy так, {}це порожнє закриття (екземпляр закриття) як за замовчуванням (нічого не робить), (Int) -> Voidце визначення закриття.
Джейк Лін

Відповіді:


871

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

Приклад: Створення асинхронного мережевого запиту

Якщо ви робите запит асинхронної мережі ви зробити хочете укупорочное зберегти selfдля коли запит закінчується. Цей об'єкт може бути інакше розміщений, але ви все ще хочете мати можливість обробляти запит.

Коли використовувати unowned selfабоweak self

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

У конкретному випадку закриття потрібно просто усвідомити, що будь-яка змінна, на яку посилається всередині неї, стає закритою. Поки закриття навколо, ці об’єкти гарантовано знаходяться навколо. Єдиний спосіб зупинити це право власності - це зробити [unowned self]або [weak self]. Отже, якщо класу належить закриття, і воно закриває чітке посилання на цей клас, то у вас є чіткий цикл відліку між закриттям і класом. Сюди також входить, якщо клас володіє чимось, що належить до закриття.

Конкретно у прикладі з відео

У прикладі на слайді TempNotifierналежить закриття через onChangeзмінну member. Якби вони не оголосили selfтаке unowned, закриття також було б власним selfстворенням сильного еталонного циклу.

Різниця між unownedіweak

Різниця між unownedі в weakтому, що weakдекларується як необов’язковий, поки unownedне є. Заявивши це, weakви отримаєте справу з тим, що в якийсь момент він може бути нульовим у закритті. Якщо ви спробуєте отримати доступ до unownedзмінної, яка виявляється нульовою, вона зламає всю програму. Тому використовуйте лише unownedтоді, коли ви впевнені, що змінна завжди буде навколо, коли закриття буде навколо


1
Привіт. Чудова відповідь. Я намагаюся зрозуміти невідомий я. Причин використовувати слабке почуття просто «я стає необов’язковим» для мене недостатньо. Чому я спеціально хочу використовувати «безхазяйний я» stackoverflow.com/questions/32936264 / ...

19
@robdashnash, Перевага використання невідомого себе полягає в тому, що вам не доведеться розгортати необов'язковий код, який може бути непотрібним кодом, якщо ви точно знаєте за дизайном, що він ніколи не буде нульовим. Зрештою, невідомий самості використовується для стислості і, можливо, також як натяк майбутнім розробникам на те, що ви ніколи не очікуєте нульового значення.
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]ви можете побачити на деяких прикладах закриття, що посилаються на абсолютно неспоріднений (довічний) об'єкт делегата.

Фактичне використання

Отже, що ви / мусите фактично використовувати більшу частину часу?

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

Невідоме швидше і дозволяє змінювати незмінність і непридатність.

Якщо вам не потрібен слабкий, не використовуйте його.

Більше про невідомі *внутрішні розробки ви знайдете тут .

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


26
Мені набридло чути пояснення папуги "використовуйте тиждень, якщо самоврядування може бути нульовим, використовуйте невідомий, коли він ніколи не може бути нульовим". Гаразд ми це отримали - почули це мільйон разів! Ця відповідь насправді копає глибше, коли самовизначення може бути нульовим простою англійською мовою, що безпосередньо відповідає на питання ОП. Дякую за це чудове пояснення !!
TruMan1

Дякую @ TruMan1, я фактично пишу повідомлення про це, яке незабаром з’явиться у моєму блозі, оновлю відповідь за посиланням.
Умберто Раймонді

1
Гарна відповідь, дуже практична. Я натхненний переключити деякі з моїх слабких показників на продуктивність на невідомі.
original_username

"Тривалість завершення роботи не залежить від змінної" У вас є друкарська помилка?
Мед

1
Якщо закриття завжди має такий самий термін служби, як і батьківський об'єкт, чи не враховуватиметься підрахунок посилань при знищенні об'єкта? Чому ви не можете просто використовувати «себе» в цій ситуації, а не турбуватися з невідомими чи слабкими?
LegendLength

105

Я думав, що додаю конкретні приклади спеціально для контролера перегляду. Багато пояснень, не тільки тут, на 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.
     }

17
На це потрібно більше грошей. Два вагомі приклади, що показують, як закриття натискання кнопок не буде існувати поза тривалістю роботи контролера перегляду, і тому вони можуть використовувати невідомі, але більшість мережевих викликів, що оновлюють інтерфейс користувача, повинні бути слабкими.
Тім Фуква

2
Тож для уточнення, чи завжди ми використовуємо невідомий чи слабкий під час виклику себе в блок закриття? Або є час, коли ми не будемо називати слабкими / невідомими? Якщо так, чи можете ви також навести приклад для цього?
luke

Дуже дякую.
Шон Баек

1
Це дало мені більш глибоке розуміння щодо [слабкого Я] та [невідомого себе] Дякую чимало @possen!
Томмі

Це чудово. що робити, якщо у мене є анімація, яка базується на взаємодії з користувачем, але потрібен певний час. Потім користувач переходить на інший viewController. Я думаю, що в такому випадку я все-таки повинен використовувати, weakа не unownedправильно?
Мед

67

Якщо самості може бути нульовим у закритті, використовуйте [слабке " .

Якщо самості ніколи не буде нульовим у закритті, скористайтеся [невідомим " .

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

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


50

Ось блискучі цитати з Форумів розробників Apple, описані смачні деталі:

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

unowned(safe)- це невласницьке посилання, яке підтверджує при доступі, що об’єкт ще живий. Це щось на зразок слабкої необов'язкової посилання, яка неявно розгортається x!кожного разу, коли до неї звертаються. unowned(unsafe)це як __unsafe_unretainedу ARC - це невласне посилання, але немає часу перевірки, чи об’єкт ще доступний для доступу, тож звисаючі посилання потраплять у пам’ять сміття. unownedзавжди синонім в unowned(safe)даний час, але намір полягає в тому, що вона буде оптимізована для unowned(unsafe)в -Ofast збірки , коли Виконавча перевіряє відключені.

unowned проти weak

unownedнасправді використовується набагато простіша реалізація, ніж weak. Об'єкти Native Swift мають два відліку, а unowned посилання замінюють невідомий підрахунок замість сильного відліку . Об'єкт деініціалізується, коли його сильний відліку досягає нуля, але він фактично не розміщений, поки невідомий контрольний підрахунок також не досягне нуля. Це спричиняє затримку пам'яті дещо довше, коли є невідомі посилання, але, як правило, це не проблемаunowned використовується тому, що пов'язані об'єкти в будь-якому випадку повинні мати майже рівний час життя, і це набагато простіше і нижче, ніж реалізація на базі столової таблиці, що використовується для нульовання слабких посилань.

Оновлення: У сучасному Swift weakвнутрішньо використовується той же механізм, що unownedі у . Отже, це порівняння є невірним, оскільки порівнює Objective-C weakзі Swift unonwed.

Причини

Яка мета збереження пам’яті живою після того, як володіння посиланнями досягне 0? Що відбувається, якщо код намагається щось зробити з об'єктом за допомогою невідомої посилання після його деніціалізації?

Пам'ять зберігається в живих, щоб зберегти її кількість залишається доступною. Таким чином, коли хтось намагається зберегти чітке посилання на невідомий об'єкт, час виконання може перевірити, чи є потужний контрольний коефіцієнт більший за нуль, щоб забезпечити безпечне утримання об'єкта.

Що відбувається з володінням або невідомими посиланнями, якими володіє об'єкт? Чи відмежовується їхнє життя від об'єкта, коли він денітинізований, або їх пам'ять також зберігається, поки об'єкт не буде розміщений після звільнення останньої невідомої посилання?

Усі ресурси, що належать об'єкту, вивільняються, як тільки вивільняється остання потужна посилання на об'єкт, і його виконання. Невідомі посилання зберігають живу пам’ять - окрім заголовка з підрахунками посилань, його вміст є непотрібним.

Збуджений, так?


38

Тут є кілька чудових відповідей. Але останні зміни щодо того, як Swift впроваджує слабкі посилання, повинні змінити слабке життя кожного у порівнянні з невідомими рішеннями щодо самостійного використання. Раніше, якщо вам потрібна найкраща ефективність використання невідомого «я», було вищим за слабке «я», доки ви могли бути впевнені, що «я ніколи не буде нульовим», оскільки доступ до невідомого «я» набагато швидший, ніж доступ до слабкого «я».

Але Майк Еш задокументував, як Свіфт оновив реалізацію слабких варіантів для використання бічних столів і як це суттєво покращує слабку самодіяльність.

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

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

Помилки та збої є найбільш трудомісткою, болісною і дорогою частиною програмування. Зробіть все можливе, щоб написати правильний код і уникати їх. Рекомендую прийняти за правило ніколи не застосовувати розгортання факультативів і ніколи не використовувати невідоме «я» замість слабкого «я». Ви нічого не втратите, не вистачаючи часу, коли сила, що розгортається, і невідома самості фактично є безпечними. Але ви отримаєте багато від усунення важких для пошуку та налагодження збоїв та помилок.


Дякую за оновлення та амін на останньому абзаці.
девіз

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якщо ви можете гарантувати, що посилання не буде нульовим в момент його використання, використовуйте невідоме. Якщо ні, то вам слід використовувати слабкі.

Пояснення:

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

"Невідомі Слабі та невідомі посилання поводяться аналогічно, але НЕ є однаковими."

Невідомі посилання, як і слабкі посилання, не збільшують кількість збережених об'єктів, які передаються. Однак у Swift невідома посилання має додаткову перевагу від того, що він не є факультативним . Це дозволяє їм легше керувати, а не вдаватися до використання необов'язкового прив'язки. Це не на відміну від Неявно розгорнутих факультативів. Крім того, невідомі посилання є ненульовими . Це означає, що коли об'єкт розміщений, він не нульовий показник не вказує на нуль. Це означає, що використання невідомих посилань в деяких випадках може призвести до звисаючих покажчиків. Для вас дурники, які пам’ятають дні Objective-C, як я, невідомі посилання відображають на небезпечні посилання.

Ось де це стає трохи заплутаним.

І слабкі, і невідомі посилання не збільшують кількість утримань.

Вони можуть бути використані для порушення циклів утримування. То коли ж ми їх будемо використовувати ?!

Відповідно до документів 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]

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.