Una perdita di memoria si verifica in un’app iOS quando la memoria che non è più in uso non viene cancellata correttamente e continua a occupare spazio. Ciò può danneggiare le prestazioni dell’app e, alla fine, quando l’app esaurisce la memoria disponibile, causerà un arresto anomalo. Per capire meglio come si verificano perdite di memoria, è importante prima sapere come le app iOS gestiscono la loro memoria. Diamo un’occhiata a come prevenire perdite di memoria nelle chiusure swift.

Conteggio automatico dei riferimenti

In iOS, la memoria condivisa viene gestita facendo in modo che ogni oggetto tenga traccia di quanti altri oggetti hanno un riferimento ad esso. Una volta che questo conteggio di riferimento raggiunge 0, il che significa che non ci sono più riferimenti all’oggetto, può essere tranquillamente cancellato dalla memoria. Come implicherebbe il nome, questo conteggio dei riferimenti è necessario solo per i tipi di riferimento, mentre i tipi di valore non richiedono la gestione della memoria. Il conteggio dei riferimenti è stato eseguito manualmente in passato chiamando retain su un oggetto per aumentare il numero di riferimenti e quindi chiamando release sull’oggetto per diminuire il numero di riferimenti. Questo codice era quasi interamente piastra caldaia, noioso da scrivere e errore incline. Quindi, come la maggior parte delle attività umili, è diventato automatizzato tramite il conteggio automatico dei riferimenti (ARC) che rende necessarie le chiamate di conservazione e rilascio per tuo conto in fase di compilazione.

Anche se ARC ha ridotto la necessità di preoccuparsi della gestione della memoria, può comunque creare perdite di memoria rapide ogni volta che ci sono riferimenti circolari. Ad esempio, diciamo che abbiamo una classe Person che ha una proprietà per un apartment e la classe Apartment ha una proprietà Person denominata tenant:

class Person { var name: String var age: Int init(name: String, age: Int) {…} var apartment: Apartment?}class Apartment { let unit: String init(unit: String) {…} var tenant: Person?}let person = Person(name: "Bob", age: 30)let unit4A = Apartment(unit: "4A")person.apartment = unit4Aunit4A.tenant = person

Quando creiamo un nuovo Person e Apartment e li assegniamo alle proprietà degli altri, ora si riferiscono l’un l’altro in modo circolare. I riferimenti circolari possono verificarsi anche con più di due oggetti. In questa situazione, sia Person che Apartment tengono un riferimento all’altro, quindi in questo caso nessuno dei due arriverà mai a un conteggio di riferimento pari a 0 e si terrà l’un l’altro in memoria, anche se nessun altro oggetto ha riferimenti a nessuno di essi. Questo è noto come ciclo di conservazione e causa una perdita di memoria.

Related: Tre caratteristiche che sarebbero Real Swifty

Il modo in cui ARC si occupa dei cicli di retain è quello di avere diversi tipi di riferimenti: strong, weak e unowned. I riferimenti forti sono quelli di cui abbiamo già parlato e questi riferimenti aumentano il numero di riferimenti di un oggetto. I riferimenti deboli, d’altra parte, pur continuando a fornire riferimenti all’oggetto, non aumentano il numero di riferimenti. Quindi, se dovessimo prendere la proprietà tenant su Apartment e renderla invece un riferimento debole, interromperebbe il nostro ciclo di conservazione:

class Apartment { let unit: String init(unit: String) {…} weak var tenant: Person?}

Ora il nostro oggetto Person tiene ancora in memoria il suo Apartment, ma il contrario non è più vero. Quindi, quando l’ultimo riferimento forte a Person è sparito, il suo conteggio dei riferimenti scenderà a 0 e rilascerà il suo Apartment, il cui conteggio dei riferimenti verrà anche eliminato a 0, e entrambi possono essere correttamente cancellati dalla memoria.

In Swift, i riferimenti deboli devono essere facoltativi var s perché, se non si assume la responsabilità di mantenere un oggetto in memoria, non è possibile garantire che l’oggetto non cambierà o lascerà memoria. È qui che entra in gioco il terzo tipo di riferimento. i riferimenti unowned sono come riferimenti deboli, tranne che possono essere non opzionali let s, ma questi dovrebbero essere usati solo quando si è sicuri che l’oggetto non dovrebbe mai essere nil. Come gli optionals forzati, stai dicendo al compilatore ” Non preoccuparti, ho capito. Fidati di me.”Ma come i riferimenti deboli, il riferimento unowned non sta facendo nulla per mantenere l’oggetto in memoria e se lascia la memoria e provi ad accedervi, la tua app si bloccherà. Di nuovo, proprio come gli optionals forzati.

Mentre i cicli di conservazione sono facili da vedere con due oggetti che puntano l’uno verso l’altro, sono più difficili da vedere quando sono coinvolte chiusure in Swift, ed è qui che ho visto accadere la maggior parte dei cicli di conservazione.

Evitare i cicli di conservazione nelle chiusure

È importante ricordare che le chiusure sono tipi di riferimento in Swift e possono causare cicli di conservazione altrettanto facilmente, se non più facilmente, delle classi. Affinché una chiusura venga eseguita in un secondo momento, è necessario mantenere tutte le variabili necessarie per l’esecuzione. Analogamente alle classi, una chiusura acquisisce i riferimenti come forti per impostazione predefinita. Un ciclo di conservazione con una chiusura sarebbe simile a questo:

class SomeObject { var aClosure = { self.doSomething() } ...}

Related: quando dovrei usare blocchi e chiusure o delegati per i callback?

 Nuovo call-to-action

In questo caso, la classe SomeObject ha un forte riferimento a aClosure e aClosure ha catturato fortemente self (l’istanza SomeObject). Questo è il motivo per cui Swift ti fa sempre aggiungere self. esplicitamente mentre sei in una chiusura per impedire ai programmatori di catturare accidentalmente self senza rendersene conto, e quindi molto probabilmente causando un ciclo di conservazione.

Per avere una chiusura cattura variabili come deboli o unowned, è possibile dare le istruzioni di chiusura su come catturare determinate variabili:

class SomeObject { var aClosure = { in self.doSomething() delegate?.doSomethingElse() } ...}

Maggiori informazioni sulla sintassi di chiusura qui.

La maggior parte degli esempi di chiusura che ho visto in tutorial o esempi sembrano catturare self come unowned e chiamarlo un giorno, poiché catturare self come debole lo rende facoltativo, che può essere più scomodo da lavorare. Ma come abbiamo imparato prima, questo è intrinsecamente pericoloso poiché ci sarà un crash se self non è più in memoria. Questo non è molto diverso dalla semplice forza di scartare tutte le variabili opzionali perché non si vuole fare il lavoro per scartarle in modo sicuro. A meno che tu non possa essere sicuro che self sarà in giro finché la tua chiusura è, dovresti provare a catturarlo debole. Se hai bisogno di un self non opzionale all’interno della tua chiusura, considera di utilizzare un if let o guard let per ottenere un self forte e non opzionale all’interno della chiusura. Poiché hai creato questo nuovo riferimento forte all’interno della chiusura Swift, non creerai un ciclo di conservazione poiché questo riferimento verrà rilasciato alla fine della chiusura:

var aClosure = { in if let strongSelf = self { doSomethingWithNonOptional(strongSelf) doSomethingElseNonOptional(strongSelf) }}

o ancora meglio:

var aClosure = { in guard let strongSelf = self else { return } doSomethingWithNonOptional(strongSelf) doSomethingElseNonOptional(strongSelf)}

Catturare Auto fortemente

Sebbene sia buona pratica catturare self debolmente nelle chiusure, non è sempre necessario. Le chiusure che non vengono mantenute da self possono catturarlo fortemente senza causare un ciclo di conservazione. Alcuni esempi comuni di questo sono:

Lavorare con DispatchQueue s in GCD

DispatchQueue.main.async { self.doSomething() // Not a retain cycle}

Lavorare con UIView.animate(withDuration:)

UIView.animate(withDuration: 1) { self.doSomething() // Not a retain cycle}

Il primo non è un ciclo di conservazione poiché self non mantiene il singleton DispatchQueue.main. Nel secondo esempio, UIView.animate(withDuration:) è un metodo di classe, che anche self non ha alcuna parte nel mantenimento.

Catturare self fortemente in queste situazioni non causerà un ciclo di conservazione, ma potrebbe anche non essere quello che vuoi. Ad esempio, tornando a GCD:

DispatchQueue.main.asyncAfter(deadline: .now() + 60) { self.doSomething()}

Questa chiusura non verrà eseguita per altri 60 secondi e manterrà self fino a quando non lo farà. Questo potrebbe essere il comportamento che desideri, ma se vuoi che self sia in grado di lasciare memoria durante questo periodo, sarebbe meglio catturarlo debolmente ed eseguirlo solo se self è ancora in giro:

DispatchQueue.main.asyncAfter(deadline: .now() + 60) { in self?.doSomething()}

Un altro posto interessante in cui self non avrebbe bisogno di essere catturato fortemente è nelle variabili lazy, che non sono chiusure, poiché verranno eseguite una volta (o mai) e quindi rilasciate in seguito:

lazy var fullName = { return self.firstName + " " + self.lastName }()

Tuttavia, se una variabile lazy è una chiusura, avrebbe bisogno di catturare l’auto debolmente. Un buon esempio di questo viene dalla guida al linguaggio di programmazione Swift:

class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { in if let text = self.text { return "<(self.name)>(text)</(self.name)>" } else { return "<(self.name) />" } }}

Questo è anche un buon esempio di un’istanza che è ragionevole utilizzare un riferimento unowned, poiché la chiusura asHTML dovrebbe esistere finché lo fa HTMLElement, ma non più.

TL;DR

Quando si lavora con le chiusure in Swift, essere consapevoli di come si catturano le variabili, in particolare self s. Se self mantiene la chiusura in qualsiasi modo, assicurarsi di catturarla debolmente. Cattura solo le variabili come unowned quando puoi essere sicuro che saranno in memoria ogni volta che viene eseguita la chiusura, non solo perché non vuoi lavorare con un selfopzionale. Questo ti aiuterà a prevenire perdite di memoria nelle chiusure Swift, portando a migliori prestazioni dell’app.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.