Qual è la differenza tra un riferimento debole e un riferimento sconosciuto?


240

Swift ha:

  • Riferimenti forti
  • Riferimenti deboli
  • Riferimenti noti

In che modo un riferimento sconosciuto è diverso da un riferimento debole?

Quando è sicuro utilizzare un riferimento sconosciuto?

I riferimenti sconosciuti rappresentano un rischio per la sicurezza come i puntatori penzolanti in C / C ++?



La mia esperienza è quella di utilizzare unownedper le classi che controlliamo, per le classi Apple, weakperché non possiamo garantire con certezza cosa fa
onmyway133

@NoorAli o "ownedBy" come riferimento "non proprietario" indica spesso il proprietario.
Ian Ringrose,

1
NOTA: ci sono importanti implicazioni sulle prestazioni di cui tenere conto con ciascuno di questi riferimenti: stackoverflow.com/questions/58635303/…
Epic Byte

@EpicByte A volte vale la pena un GC completo come Java o C #.
Ian Ringrose

Risposte:


361

Sia weake unownedriferimenti non creare una strongpresa sull'oggetto di cui (aka non aumentano il conteggio conservano al fine di evitare ARC dal deallocando l'oggetto di cui).

Ma perché due parole chiave? Questa distinzione ha a che fare con il fatto che i Optionaltipi sono integrati nel linguaggio Swift. Per farla breve: i tipi opzionali offrono sicurezza della memoria (questo funziona magnificamente con le regole del costruttore di Swift - che sono rigorose per fornire questo vantaggio).

Un weakriferimento ne consente la possibilitànil (ciò si verifica automaticamente quando l'oggetto referenziato viene deallocato), pertanto il tipo di proprietà deve essere facoltativo, pertanto l'utente, in quanto programmatore, è tenuto a verificarlo prima di utilizzarlo (sostanzialmente il compilatore ti costringe, per quanto possibile, a scrivere un codice sicuro).

Un unownedriferimento presume che non diventerà mai nildurante la sua vita. È necessario impostare un riferimento non noto durante l'inizializzazione; ciò significa che il riferimento verrà definito come un tipo non opzionale che può essere utilizzato in modo sicuro senza controlli. Se in qualche modo l'oggetto a cui viene fatto riferimento è deallocato, l'app andrà in crash quando verrà utilizzato il riferimento non proprietario.

Dai documenti Apple :

Utilizzare un riferimento debole ogni volta che è valido affinché quel riferimento diventi nullo ad un certo punto durante la sua vita. Al contrario, utilizzare un riferimento non noto quando si sa che il riferimento non sarà mai zero dopo che è stato impostato durante l'inizializzazione.

Nei documenti, ci sono alcuni esempi che discutono dei cicli di mantenimento e di come interromperli. Tutti questi esempi sono estratti dai documenti .

Esempio della weakparola chiave:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    weak var tenant: Person?
}

E ora, per alcune opere ASCII (dovresti andare a vedere i documenti - hanno dei diagrammi carini):

Person ===(strong)==> Apartment
Person <==(weak)===== Apartment

Il PersoneApartment esempio mostra una situazione in cui due proprietà, entrambe le quali possono essere nulle, hanno il potenziale per causare un forte ciclo di riferimento. Questo scenario è meglio risolto con un riferimento debole. Entrambe le entità possono esistere senza avere una stretta dipendenza dall'altra.

Esempio della unownedparola chiave:

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) { self.number = number; self.customer = customer }
}

In questo esempio, a Customerpuò o meno avere a CreditCard, ma a CreditCard sarà sempre associato a Customer. Per rappresentarlo, la Customerclasse ha una cardproprietà opzionale , ma la CreditCardclasse ha una proprietà non opzionale (e non posseduta) customer.

Customer ===(strong)==> CreditCard
Customer <==(unowned)== CreditCard

L' esempio Customere CreditCardmostra una situazione in cui una proprietà che può essere nulla e un'altra proprietà che non può essere nulla ha il potenziale per causare un forte ciclo di riferimento. Questo scenario viene risolto al meglio con un riferimento sconosciuto.

Nota di Apple:

I riferimenti deboli devono essere dichiarati come variabili, per indicare che il loro valore può cambiare in fase di esecuzione. Un riferimento debole non può essere dichiarato come costante.

Esiste anche un terzo scenario in cui entrambe le proprietà dovrebbero sempre avere un valore e nessuna proprietà dovrebbe mai essere nulla una volta completata l'inizializzazione.

E ci sono anche i classici scenari del ciclo di conservazione da evitare quando si lavora con le chiusure.

Per questo, ti incoraggio a visitare i documenti Apple o a leggere il libro .


3
Questo è in qualche modo banale ma trovo un po 'confuso l'esempio dell'appartamento e della persona che presenta anche una soluzione aggiuntiva per interrompere il forte ciclo di riferimento. L'appartamento di una persona è facoltativo e pertanto può essere nullo, così come l'inquilino di un appartamento è facoltativo e pertanto può essere nullo, quindi entrambe le proprietà possono essere definite come deboli. ``
Justin Levi Winter,

class Person {let name: String init (name: String) {self.name = name} debole var appartamento: Appartamento? } classe Appartamento {let number: Int init (number: Int) {self.number = number} tenant var debole: Persona? }
Justin Levi Winter,

3
Qual è la differenza tra weak var Person?vs var Person??
Decano del

4
@JustinLevi, se si dichiarano entrambe le proprietà deboli, esiste la possibilità che vengano deallocate. La persona mantiene un forte riferimento all'appartamento, quindi l'appartamento non verrà deallocato. Se l'appartamento avesse lo stesso riferimento forte verso la Persona, creerebbe un ciclo di mantenimento, che può essere interrotto dal programmatore in fase di esecuzione se ne è a conoscenza, ma per il resto è solo una perdita di memoria. Questo è tutto il clamore di forte, debole e sconosciuto: la gestione della memoria ad un livello superiore, perché ARC fa tutto il sporco per noi. Evitare i cicli di mantenimento è il nostro compito.
Ilea Cristian,

1
L'unico vantaggio di non conosciuti rispetto a quelli deboli è che non è necessario scartarli e utilizzare una costante? Esiste un caso in cui non si può usare il debole e si può usare solo il non posseduto?
Alan,

29

Q1. In che modo un "riferimento noto" è diverso da un "riferimento debole"?

Riferimento debole:

Un riferimento debole è un riferimento che non mantiene una stretta presa sull'istanza a cui fa riferimento e quindi non impedisce ad ARC di disporre dell'istanza di riferimento. Poiché i riferimenti deboli non possono avere "nessun valore", è necessario dichiarare che ogni riferimento debole ha un tipo facoltativo. (Apple Docs)

Riferimenti noti:

Come i riferimenti deboli, un riferimento sconosciuto non mantiene una stretta presa sull'istanza a cui si riferisce. A differenza di un riferimento debole, tuttavia, si presume che un riferimento non noto abbia sempre un valore. Per questo motivo, un riferimento non proprietario viene sempre definito come un tipo non facoltativo. (Apple Docs)

Quando usarli ciascuno:

Utilizzare un riferimento debole ogni volta che è valido affinché quel riferimento diventi nullo ad un certo punto durante la sua vita. Al contrario, utilizzare un riferimento non noto quando si sa che il riferimento non sarà mai zero dopo che è stato impostato durante l'inizializzazione. (Apple Docs)


Q2. Quando è sicuro utilizzare un "riferimento non riconosciuto"?

Come indicato sopra, si presume che un riferimento non noto abbia sempre un valore. Quindi dovresti usarlo solo quando sei sicuro che il riferimento non sarà mai zero. I documenti Apple illustrano un caso d'uso per riferimenti non noti attraverso il seguente esempio.

Supponiamo di avere due classi Customere CreditCard. Un cliente può esistere senza una carta di credito, ma non esiste una carta di credito senza un cliente, cioè si può presumere che una carta di credito abbia sempre un cliente. Quindi, dovrebbero avere la seguente relazione:

class Customer {
    var card: CreditCard?
}

class CreditCard {
    unowned let customer: Customer
}

Q3. I "riferimenti non noti" fanno riferimento a un rischio per la sicurezza come "puntatori penzolanti" in C / C ++

Io non la penso così.

Poiché i riferimenti non noti sono solo riferimenti deboli che hanno un valore garantito, non dovrebbe essere in alcun modo un rischio per la sicurezza. Tuttavia, se si tenta di accedere a un riferimento non proprietario dopo che l'istanza a cui fa riferimento è stata deallocata, si attiverà un errore di runtime e l'app andrà in crash.

Questo è l'unico rischio che vedo con esso.

Link ad Apple Docs


il tuo programma di esempio Q2 semplice da capire su sconosciuto ... grazie .. puoi aggiungere lo stesso tipo di esempio per debole e forte ..
Ranjith Kumar

Eccellente. Grazie.
Swifty McSwifterton,

Puoi includere un esempio comune di non posseduto o debole?
Miele,

Considera gli oggetti padre e figlio, se il figlio non può esistere senza un padre, usa unownedper la proprietà del padre nella classe figlio. debole è viceversa. Bella spiegazione @myxtic! unownedi riferimenti sono solo weakriferimenti che hanno un valore garantito!
Saif,

26

Se il potrebbe essere nullo nella chiusura usare [sé debole] .

Se il non sarà mai nullo nella chiusura, usa [sé sconosciuto] .

Se si arresta in modo anomalo quando si utilizza [un sé sconosciuto], il sé è probabilmente nullo a un certo punto in quella chiusura e probabilmente è necessario utilizzare invece [sé debole] .

Dai un'occhiata agli esempi sull'uso di chiusure forti , deboli e sconosciute :

https://developer.apple.com/library/ios/documentation/swift/conceptual/swift_programming_language/AutomaticReferenceCounting.html


7
Perché non usare solo il debole anche se il sé non può mai essere nullo, nessun danno fatto bene?
Boon,

4
ciao @Boon - questa è davvero la domanda critica.
Fattie,

[weak self] => Se uso la chiusura all'interno di viewDidLoad (), come posso selfessere nullo?
Hassan Tareq,

@HassanTareq, penso che un paio di buoni esempi siano citati nell'articolo, menzionato sopra. Controlla la sezione "Risoluzione di cicli di riferimento forti per chiusure", esp. Citazione: "Swift richiede di scrivere self.someProperty o self.someMethod () (anziché solo someProperty o someMethod ()) ogni volta che si fa riferimento a un membro di sé all'interno di una chiusura. Ciò consente di ricordare che è possibile acquisire incidente." Estratto da: Apple Inc. "Il linguaggio di programmazione Swift (Swift 4)." iBooks. itunes.apple.com/de/book/the-swift-programming-language-swift-4/… "
Nick Entin

1
@Boon Se si utilizza sempre debole, il compilatore forzerà a verificare la presenza di optional prima dell'uso. Nel caso in cui non sia stato inserito quel controllo, verrà visualizzato un errore di tempo di compilazione. Non c'è altro danno.
Vikas Mishra

5

Estratti dal collegamento

Pochi punti conclusivi

  • Per determinare se devi anche preoccuparti di cose forti, deboli o sconosciute, chiedi: "Ho a che fare con i tipi di riferimento". Se stai lavorando con Struct o Enum, ARC non gestisce la memoria per quei tipi e non devi nemmeno preoccuparti di specificare debole o sconosciuto per quelle costanti o variabili.
  • I riferimenti forti vanno bene nelle relazioni gerarchiche in cui il genitore fa riferimento al figlio, ma non viceversa. In effetti, i riferimenti forti sono il tipo di riferimento più appropriato per la maggior parte del tempo.
  • Quando due istanze sono opzionalmente correlate tra loro, assicurarsi che una di queste istanze contenga un riferimento debole all'altra.
  • Quando due istanze sono correlate in modo tale che una delle istanze non possa esistere senza l'altra, l'istanza con la dipendenza obbligatoria deve contenere un riferimento sconosciuto all'altra istanza.

1

Entrambi weake unownedriferimenti non influiranno sul conteggio dei riferimenti dell'oggetto. Ma il riferimento debole sarà sempre facoltativo, cioè può essere nullo, mentre i unownedriferimenti non possono mai essere nulli, quindi non saranno mai facoltativi. Quando si utilizza un riferimento opzionale, sarà sempre necessario gestire la possibilità che l'oggetto sia nullo. Nel caso di un riferimento sconosciuto, dovrai assicurarti che l'oggetto non sia mai zero. L'uso di un riferimento sconosciuto a un oggetto zero sarà simile allo scartamento forzato di un facoltativo nullo.

Detto questo, è sicuro utilizzare un riferimento non riconosciuto in cui si è certi che la durata dell'oggetto sia maggiore di quella di riferimento. In caso contrario, è preferibile utilizzare un riferimento debole.

Per quanto riguarda la terza parte della domanda, non credo che un riferimento sconosciuto sia simile a un puntatore penzolante. Quando parliamo di conteggio dei riferimenti, di solito ci riferiamo al forte conteggio dei riferimenti dell'oggetto. Allo stesso modo swift mantiene il conteggio dei riferimenti non noti e i conteggi dei riferimenti deboli per l'oggetto (punti di riferimento deboli a qualcosa chiamato "tabella laterale" anziché l'oggetto stesso). Quando il conteggio di riferimento forte raggiunge lo zero, l'oggetto viene deinizializzato, ma non può essere deallocato se il conteggio di riferimento non proprietario è maggiore di zero.

Ora un puntatore penzolante è qualcosa che punta a una posizione di memoria che è già stata deallocata. Ma in fretta poiché la memoria può essere deallocata solo fintanto che esiste un riferimento sconosciuto all'oggetto, non può causare un puntatore penzolante.

Ci sono molti articoli che discutono più rapidamente della gestione della memoria. Eccone uno.


0

I riferimenti noti sono un tipo di riferimento debole utilizzato nel caso di una relazione Same-Lifetime tra due oggetti, quando un oggetto dovrebbe essere sempre e solo posseduto da un altro oggetto. È un modo per creare un legame immutabile tra un oggetto e una delle sue proprietà.

Nell'esempio fornito nel rapido video intermedio del WWDC, una persona possiede una carta di credito e una carta di credito può avere un solo titolare. Sulla carta di credito, la persona non dovrebbe essere una proprietà opzionale, perché non si desidera avere una carta di credito che fluttua con un solo proprietario. È possibile interrompere questo ciclo rendendo la proprietà del titolare sul credito un riferimento debole, ma ciò richiede anche di renderlo facoltativo e variabile (anziché costante). Il riferimento sconosciuto in questo caso significa che sebbene CreditCard non abbia una partecipazione proprietaria in una Persona, la sua vita dipende da essa.

class Person {
    var card: CreditCard?
}

class CreditCard {

    unowned let holder: Person

    init (holder: Person) {
        self.holder = holder
    }
}

link al video o titolo wwdc?
Osa,

-2

Usa unownedquando sei sicuro di selfnon poter mai essere nilnel punto in cui accedi selfa quel punto.

Esempio (puoi ovviamente aggiungere il target direttamente da MyViewController, ma di nuovo, è un semplice esempio) .:

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let myButton = MyButton { [unowned self] in
            print("At this point, self can NEVER be nil. You are safe to use unowned.")
            print("This is because myButton can not be referenced without/outside this instance (myViewController)")
        }
    }
}

class MyButton: UIButton {
    var clicked: (() -> ())

    init(clicked: (() -> ())) {
        self.clicked = clicked

        // We use constraints to layout the view. We don't explicitly set the frame.
        super.init(frame: .zero)

        addTarget(self, action: #selector(clicked), for: .touchUpInside)
    }

    @objc private func sendClosure() {
        clicked()
    }
}

Utilizzare weakquando esiste una possibilità selfpuò essere nilnel punto in cui si accede self.

Esempio:

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        NetworkManager.sharedInstance.receivedData = { [weak self] (data) in
            print("Can you guarentee that self is always available when the network manager received data?")
            print("Nope, you can't. Network manager will be alive, regardless of this particular instance of MyViewController")
            print("You should use weak self here, since you are not sure if this instance is still alive for every")
            print("future callback of network manager")
        }
    }
}

class NetworkManager {

    static let sharedInstance = NetworkManager()

    var receivedData: ((Data) -> ())?

    private func process(_ data: Data) {
        // process the data...

        // ... eventually notify a possible listener.
        receivedData?(data)
    }
}

Contro di unowned:

  • Più efficiente che debole
  • Puoi (bene, sei costretto) a contrassegnare l'istanza come immutabile (non più da Swift 5.0).
  • Indica al lettore del tuo codice: questa istanza ha una relazione con X e non può vivere senza di essa, ma se X è sparito, anch'io sono andato.

Contro di weak:

  • Più sicuro di sconosciuto (poiché non può bloccarsi).
  • Può creare una relazione con X che va in entrambi i modi, ma entrambi possono vivere l'uno senza l'altro.

Se non sei sicuro, usa weak. Aspetta , voglio chiedere qui su StackOverflow cosa dovresti fare nel tuo caso! Usare debole tutto il tempo quando non dovresti è solo fonte di confusione per te e per il lettore del tuo codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.