Czy powinniśmy zawsze używać [nieposiadanego siebie] w zamknięciu w Swift

467

Podczas sesji WWDC 2014 403 Intermediate Swift i transkrypcji pojawił się następujący slajd

wprowadź opis zdjęcia tutaj

Mówca powiedział w takim przypadku, że jeśli go nie użyjemy [unowned self], nastąpi wyciek pamięci. Czy to oznacza, że ​​zawsze powinniśmy używać [unowned self]zamknięcia wewnętrznego?

W linii 64 ViewController.swift aplikacji Swift Weather nie używam [unowned self]. Ale aktualizuję interfejs za pomocą niektórych @IBOutlettakich jak self.temperaturei self.loadingIndicator. Może być OK, ponieważ wszystkie @IBOutlets, które zdefiniowałem, są weak. Ale czy ze względów bezpieczeństwa powinniśmy zawsze używać [unowned self]?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}
Jake Lin
źródło
link do obrazu jest zerwany
Daniel Gomez Rico
@ DanielG.R. Dzięki, widzę to. i.stack.imgur.com/Jd9Co.png
Jake Lin
2
O ile się nie mylę, przykład podany na slajdzie jest niepoprawny - onChangepowinien być [weak self]zamknięciem, ponieważ jest to właściwość publiczna (wewnętrznie, ale nadal), aby inny obiekt mógł uzyskać i przechowywać zamknięcie, utrzymując obiekt TempNotifier w pobliżu (bezterminowo, jeśli obiekt używający nie puścił onChangezamknięcia, dopóki nie zobaczy, że TempNotifierzniknął, poprzez swoje własne słabe odniesienie do TempNotifier) . Gdyby var onChange …były, private var onChange …to [unowned self]byłoby poprawne. Nie jestem jednak tego w 100% pewien; proszę mnie poprawić, jeśli się mylę.
Slipp D. Thompson
@Jake Lin `var onChange: (Int) -> Void = {}` czy nawiasy klamrowe reprezentują puste zamknięcie? tak samo jak przy definiowaniu pustej tablicy za pomocą []? Nie mogę znaleźć wyjaśnienia w dokumentach Apple.
bibscy
@bibscy tak, {}to puste zamknięcie (instancja zamknięcia) jako domyślne (nic nie robi), (Int) -> Voidto definicja zamknięcia.
Jake Lin,

Odpowiedzi:

871

Nie, zdecydowanie są chwile, w których nie chciałbyś korzystać [unowned self]. Czasami chcesz, aby zamknięcie uchwyciło siebie, aby upewnić się, że jest jeszcze w pobliżu, zanim zostanie wywołane zamknięcie.

Przykład: wykonywanie asynchronicznego żądania sieciowego

Jeśli dokonujesz asynchroniczne żądanie sieciowe Ci zrobić chcą zamknięcie zachować selfuwagę przy zakończeniu zgłoszeń. W przeciwnym razie obiekt ten mógł zostać zwolniony, ale nadal chcesz być w stanie obsłużyć zakończenie żądania.

Kiedy stosować unowned selflubweak self

Jedynym momentem, w którym naprawdę chcesz użyć [unowned self]lub [weak self]jest moment, w którym stworzyłbyś silny cykl odniesienia . Silny cykl odniesienia występuje wtedy, gdy istnieje pętla własności, w której obiekty ostatecznie posiadają siebie nawzajem (być może za pośrednictwem strony trzeciej) i dlatego nigdy nie zostaną zwolnione, ponieważ oba zapewniają wzajemne trzymanie się.

W konkretnym przypadku zamknięcia wystarczy uświadomić sobie, że każda zmienna, do której się odwołuje, jest „własnością” tego zamknięcia. Tak długo, jak zamknięcie jest w pobliżu, obiekty te są gwarantowane w pobliżu. Jedynym sposobem na zatrzymanie tej własności jest zrobienie [unowned self]lub [weak self]. Więc jeśli klasa jest właścicielem zamknięcia, a to zamknięcie zawiera silne odniesienie do tej klasy, wówczas istnieje silny cykl odniesienia między zamknięciem a klasą. Obejmuje to również, jeśli klasa posiada coś, co jest właścicielem zamknięcia.

W szczególności w przykładzie z wideo

W przykładzie na slajdzie TempNotifierjest właścicielem zamknięcia poprzez onChangezmienną elementu. Gdyby nie zadeklarowali selfjako unowned, zamknięcie również byłoby równoznaczne z selfutworzeniem silnego cyklu odniesienia.

Różnica między unownediweak

Różnica między unownedi weakjest weakzadeklarowana jako Opcjonalna, podczas gdy unownednie jest. Deklarując to, weakmożesz zająć się sprawą, że w pewnym momencie może ona być zerowa. Jeśli spróbujesz uzyskać dostęp do unownedzmiennej, która jest zerowa, spowoduje to awarię całego programu. Dlatego używaj tylko unownedwtedy, gdy jesteś pozytywny, że zmienna będzie zawsze w pobliżu, gdy zamknięcie jest w pobliżu

Drawag
źródło
1
Cześć. Świetna odpowiedź. Usiłuję zrozumieć nieprzyzwoite ja. Powód, dla którego warto używać słownika Sama, po prostu bycie „ja” staje się opcjonalne, nie jest dla mnie wystarczające. Dlaczego miałbym konkretnie chcieć korzystać z „unowned self” stackoverflow.com/questions/32936264/…
19
@robdashnash, Zaletą korzystania z nie posiadanego self jest to, że nie musisz rozpakowywać opcjonalnego, który może być niepotrzebnym kodem, jeśli wiesz na pewno, że projekt nigdy nie będzie zerowy. Ostatecznie, nieposiadane ja jest używane dla zwięzłości i być może również jako wskazówka dla przyszłych programistów, że nigdy nie spodziewasz się żadnej wartości zerowej.
Drawag
77
Przypadkiem użycia [weak self]w asynchronicznym żądaniu sieci jest kontroler widoku, w którym to żądanie jest używane do wypełnienia widoku. Jeśli użytkownik się wycofuje, nie musimy już wypełniać widoku ani nie potrzebujemy odwołania do kontrolera widoku.
David James
1
weakodniesienia są również ustawione, nilgdy obiekt jest zwalniany. unownedodniesienia nie są.
BergQuester,
1
Jestem trochę zmieszany. unownedjest używany do non-Optionalkiedy weakjest używany, Optionalwięc nasz selfjest Optionalczy non-optional?
Muhammad Nayab
193

Aktualizacja 11/2016

Napisałem artykuł na ten temat, rozszerzając tę ​​odpowiedź (patrząc na SIL, aby zrozumieć, co robi ARC), sprawdź to tutaj .

Oryginalna odpowiedź

Poprzednie odpowiedzi tak naprawdę nie zawierają prostych zasad określających, kiedy należy używać jednego nad drugim i dlaczego, więc dodam kilka rzeczy.

Nieznana lub słaba dyskusja sprowadza się do pytania o czas życia zmiennej i odnoszące się do niej zamknięcie.

szybkie słabe vs nieposiadane

Scenariusze

Możesz mieć dwa możliwe scenariusze:

  1. Zamknięcie ma taki sam okres istnienia zmiennej, więc zamknięcie będzie osiągalne tylko do momentu osiągnięcia zmiennej . Zmienna i zamknięcie mają taki sam okres użytkowania. W takim przypadku powinieneś zadeklarować odwołanie jako nienależące do właściciela . Typowym przykładem jest [unowned self]używany w wielu przykładach małych zamknięć, które robią coś w kontekście swojego rodzica i które nie są przywoływane nigdzie indziej, nie przeżywają swoich rodziców.

  2. Czas życia zamknięcia jest niezależny od jednego ze zmiennej, zamknięcie może być nadal przywoływane, gdy zmienna nie jest już osiągalna. W takim przypadku powinieneś zadeklarować referencję jako słabą i sprawdzić, czy nie jest zerowa przed użyciem (nie wymuszaj rozpakowywania). Typowym przykładem tego jest to, [weak delegate]że można zobaczyć w niektórych przykładach zamknięcia odnoszących się do całkowicie niezwiązanego (w ciągu całego życia) obiektu delegowanego.

Rzeczywiste użycie

A więc, które / które powinieneś używać przez większość czasu?

Cytując Joe Groffa z Twittera :

Unowned jest szybszy i pozwala na niezmienność i nieopcjonalność.

Jeśli nie potrzebujesz słabego, nie używaj go.

Więcej informacji na temat nieposiadanych *wewnętrznych mechanizmów znajdziesz tutaj .

* Zwykle określane również jako nieposiadane (bezpieczne), aby wskazać, że kontrole środowiska wykonawczego (które prowadzą do awarii z powodu nieprawidłowych referencji) są przeprowadzane przed uzyskaniem dostępu do nieznanych referencji.

Umberto Raimondi
źródło
26
Jestem zmęczony słyszeniem objaśnienia papugi: „Tydzień używania, jeśli jaźń może być zerowa, używaj bez właściciela, gdy nigdy nie może być zerowa”. Ok, rozumiemy - słyszeliśmy to milion razy! Ta odpowiedź w rzeczywistości prowadzi do głębszego stwierdzenia, kiedy jaźń może być zerowa zwykłym angielskim, co bezpośrednio odpowiada na pytanie OP. Dzięki za to wspaniałe wytłumaczenie !!
TruMan1,
Dzięki @ TruMan1, piszę o tym post, który wkrótce trafi na mojego bloga, zaktualizuję odpowiedź linkiem.
Umberto Raimondi
1
Ładna odpowiedź, bardzo praktyczna. Zainspirowałem się do zamiany niektórych moich wrażliwych słabych modeli na nieprzystosowane teraz.
original_username
„Czas życia zamknięcia jest niezależny od zmiennej” Czy masz literówkę?
Kochanie,
1
Jeśli zamknięcie zawsze ma taki sam okres istnienia jak obiekt nadrzędny, czy nie zostanie uwzględnione liczenie referencji, gdy obiekt zostanie zniszczony? Dlaczego nie możesz po prostu użyć „ja” w tej sytuacji zamiast zawracać sobie głowę nieposiadanymi lub słabymi?
LegendLength,
105

Pomyślałem, że dodam kilka konkretnych przykładów specjalnie dla kontrolera widoku. Wiele wyjaśnień, nie tylko tutaj na temat Przepełnienia stosu, jest naprawdę dobrych, ale pracuję lepiej z przykładami ze świata rzeczywistego (@drewag miał dobry początek na tym):

  • Jeśli masz zamknięcie do obsługi odpowiedzi z żądań sieciowych, użyj ich weak, ponieważ są one długie. Kontroler widoku może zamknąć się przed zakończeniem żądania, więc selfnie będzie już wskazywał na prawidłowy obiekt po wywołaniu zamknięcia.
  • Jeśli masz zamknięcie, które obsługuje zdarzenie na przycisku. Może to być unownedspowodowane tym, że gdy tylko kontroler widoku zniknie, przycisk i wszelkie inne elementy, do których może się odnosić, selfznikają w tym samym czasie. Blok zamknięcia również zniknie w tym samym czasie.

    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
źródło
17
Ten potrzebuje więcej pozytywnych opinii. Dwa solidne przykłady pokazujące, że zamknięcie naciśnięcia przycisku nie będzie istniało poza okresem użytkowania kontrolera widoku, a zatem może używać nieposiadanego, ale większość połączeń sieciowych aktualizujących interfejs użytkownika musi być słaba.
Tim Fuqua,
2
Czyli dla wyjaśnienia, czy zawsze nazywamy się nieposłusznymi lub słabymi, gdy nazywamy siebie w bloku zamknięcia? Czy jest czas, w którym nie nazwiemy słabego / nieposiadanego? Jeśli tak, czy mógłbyś podać przykład tego?
luke
Dziękuję bardzo.
Shawn Baek
1
To pozwoliło mi głębiej zrozumieć [słabe ja] i [nieposłuszne ja] Wielkie dzięki @possen!
Tommy
To jest świetne. co jeśli mam animację opartą na interakcji użytkownika, ale jej ukończenie zajmuje trochę czasu. A następnie użytkownik przechodzi do innego viewController. Wydaje mi się, że w takim przypadku nadal powinienem używać, weaka nie unownedprawda?
Honey,
67

Jeśli jaźń może być zerowa w zamknięciu, użyj [słabego ja] .

Jeśli ja nigdy nie będzie zerowe w zamknięciu, użyj [jaźni nieposiadanej] .

Dokumentacja Apple Swift ma świetną sekcję ze zdjęciami wyjaśniającymi różnicę między używaniem silnych , słabych i nieposiadanych w zamknięciach:

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

TenaciousJay
źródło
50

Oto genialne cytaty z forów programistów Apple opisujące pyszne szczegóły:

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)jest nie posiadającym odnośnikiem, który zapewnia przy dostępie, że obiekt wciąż żyje. To coś w rodzaju słabego opcjonalnego odwołania, które jest domyślnie rozpakowywane przy x!każdym dostępie. unowned(unsafe)jest jak __unsafe_unretainedw ARC - jest to odwołanie nie będące właścicielem, ale nie ma sprawdzania w czasie wykonywania, czy obiekt wciąż żyje w momencie dostępu, więc wiszące odniesienia sięgną do pamięci śmieci. unownedjest zawsze synonimem unowned(safe)obecnie, ale intencją jest to, że zostanie zoptymalizowany unowned(unsafe)w -Ofast kompilacjach, gdy kontrole środowiska wykonawczego są wyłączone.

unowned vs weak

unownedfaktycznie używa znacznie prostszej implementacji niż weak. Obiekty Native Swift posiadają dwie liczby referencji, a unowned referencje powodują wzrost liczby nieposiadanych referencji zamiast silnej liczby referencji . Obiekt jest deinicjalizowany, gdy jego mocna liczba referencyjna osiągnie zero, ale w rzeczywistości nie jest dezalokowana, dopóki liczba nieposiadanych referencji również nie osiągnie zera. Powoduje to, że pamięć zostaje wstrzymana nieco dłużej, gdy istnieją nieprzypisane odwołania, ale zwykle nie stanowi to problemuunowned jest używane, ponieważ powiązane obiekty i tak powinny mieć prawie równe czasy życia, a jest to znacznie prostsze i mniej kosztowne niż implementacja oparta na tabeli bocznej używana do zerowania słabych referencji.

Aktualizacja: we współczesnym Swift weakwewnętrznie używa tego samego mechanizmu, co unownedrobi . To porównanie jest niepoprawne, ponieważ porównuje Objective-C weakz Swift unonwed.

Powody

Jaki jest cel utrzymania pamięci przy życiu po osiągnięciu przez referencje 0? Co się stanie, jeśli kod spróbuje coś zrobić z obiektem przy użyciu nieznanego odwołania po jego deinicjalizacji?

Pamięć jest utrzymywana przy życiu, dzięki czemu jej liczby zatrzymań są nadal dostępne. W ten sposób, gdy ktoś próbuje zachować silne odniesienie do nie posiadanego obiektu, środowisko wykonawcze może sprawdzić, czy liczba silnych odniesień jest większa od zera, aby zapewnić bezpieczne zachowanie obiektu.

Co dzieje się z posiadaniem lub nieposiadaniem referencji przechowywanych przez obiekt? Czy ich czas życia jest oddzielony od obiektu, gdy jest on deinicjalizowany, czy też ich pamięć jest zachowywana, dopóki obiekt nie zostanie zwolniony po zwolnieniu ostatniego nieznanego odwołania?

Wszystkie zasoby należące do obiektu są zwalniane, gdy tylko ostatnie silne odwołanie do obiektu zostanie zwolnione, a jego deinit zostanie uruchomiony. Nieznane odwołania utrzymują pamięć tylko przy życiu - oprócz nagłówka z licznikiem odniesień jego zawartość jest śmieciowa.

Podekscytowany?

Valentin Shergin
źródło
38

Jest tu kilka świetnych odpowiedzi. Ale ostatnie zmiany w sposobie, w jaki Swift implementuje słabe referencje, powinny zmienić słabe ja każdego użytkownika w porównaniu do niezasłużonych decyzji o własnym użytkowaniu. Poprzednio, jeśli potrzebowałeś najlepszej wydajności, używając jaźni nieposiadanej, był lepszy od słabego ja, tak długo, jak możesz być pewien, że jaźń nigdy nie będzie zerowa, ponieważ dostęp do jaźni nieposiadanej jest znacznie szybszy niż dostęp do jaźni słabej.

Ale Mike Ash udokumentował, w jaki sposób Swift zaktualizował implementację słabych zmiennych, aby używał tabel bocznych i jak to znacznie poprawia słabą samoocenę.

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

Teraz, gdy nie ma znacznego ograniczenia wydajności dla słabego ja, uważam, że powinniśmy domyślnie używać go dalej. Zaletą słabego ja jest to, że jest opcjonalny, co znacznie ułatwia pisanie bardziej poprawnego kodu, jest to w zasadzie powód, dla którego Swift jest tak świetnym językiem. Może ci się wydawać, że wiesz, które sytuacje są bezpieczne dla korzystania z nieposiadanego siebie, ale moje doświadczenie z przeglądaniem wielu innych kodów programistów jest takie, że większość tego nie robi. Naprawiłem wiele awarii, w wyniku których nieprzydzielone ja zostało cofnięte, zwykle w sytuacjach, gdy wątek w tle kończy się po zwolnieniu kontrolera.

Błędy i awarie są najbardziej czasochłonnymi, bolesnymi i kosztownymi częściami programowania. Staraj się pisać poprawny kod i unikaj ich. Zalecam, aby regułą było, aby nigdy nie wymuszać rozpakowywania opcji i nigdy nie używać jaźni nieposiadanej zamiast słabej jaźni. Nie stracisz niczego, co zmarnuje czas, kiedy wymuszanie rozpakowywania i nieposiadane ja faktycznie są bezpieczne. Ale wiele zyskasz na eliminowaniu trudnych do znalezienia i debugowania awarii i błędów.

SafeFastExpressive
źródło
Dziękujemy za aktualizację i Amen w ostatnim akapicie.
motto
1
Więc po nowych zmianach Czy jest czas, kiedy weaknie można go użyć zamiast unowned?
Honey,
4

Według Apple-doc

  • Słabe referencje są zawsze typu opcjonalnego i automatycznie stają się zerowe, gdy instancja, do której się odwołują, jest zwolniona.

  • Jeśli przechwycone odniesienie nigdy nie stanie się zerowe, zawsze powinno zostać przechwycone jako niepowiązane odniesienie, a nie słabe odniesienie

Przykład -

    // 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)
     }
Jacek
źródło
0

Jeśli żadne z powyższych nie ma sensu:

tl; dr

Podobnie implicitly unwrapped optional, jeśli możesz zagwarantować, że odniesienie nie będzie zerowe w momencie użycia, użyj nieposiadanego. Jeśli nie, to powinieneś używać słabego.

Wyjaśnienie:

Pobrałem następujące informacje pod adresem: słaby nieposiadany link . Z tego, co zebrałem, nieposiadane ja nie może być zerowe, ale słabe ja może być, a nieposiadane ja może prowadzić do zwisających wskazówek ... czegoś niesławnego w Celu C. Mam nadzieję, że to pomoże

„NIEZNANE Słabe i nieposiadane odniesienia zachowują się podobnie, ale NIE są takie same”.

Nieznane referencje, podobnie jak słabe referencje, nie zwiększają liczby zatrzymań obiektu , do którego następuje odwołanie . Jednak w Swift nieznane odwołanie ma tę dodatkową zaletę, że nie jest Opcjonalne . To sprawia, że łatwiej nimi zarządzać niż korzystać z opcjonalnego wiązania. Podobnie jest w przypadku opcji niejawnie nieopakowanych. Ponadto nieposiadane odniesienia niezerowane . Oznacza to, że gdy obiekt zostaje zwolniony, nie zeruje wskaźnika. Oznacza to, że użycie nieznanych referencji może w niektórych przypadkach prowadzić do zwisających wskaźników. Dla was kujony, które pamiętają dni Celu C, tak jak ja, nieposiadane referencje odwzorowują niebezpieczne.

Tu zaczyna się trochę mylić.

Zarówno słabe, jak i niepowiązane referencje nie zwiększają liczby zatrzymań.

Oba można wykorzystać do przerwania cykli zatrzymania. Więc kiedy ich używamy ?!

Według dokumentów Apple :

„Stosuj słabe odniesienie, ilekroć jest ważne, aby odniesienie to stało się zerowe w pewnym momencie jego życia. I odwrotnie, użyj nieznanego odwołania, jeśli wiesz, że odniesienie nigdy nie będzie zerowe, jeśli zostanie ustawione podczas inicjalizacji. ”

Daksh Gargas
źródło
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)
        }

    }

}

Jeśli nie jesteś pewien, [unowned self] użyj [weak self]

Shourob Datta
źródło