o scurgere de memorie apare într-o aplicație iOS atunci când memoria care nu mai este utilizată nu este curățată corespunzător și continuă să ocupe spațiu. Acest lucru poate afecta performanța aplicației și, în cele din urmă, atunci când aplicația rămâne fără memorie disponibilă, va provoca un accident. Pentru a înțelege mai bine cum se întâmplă scurgerile de memorie, este important să știți mai întâi cum aplicațiile iOS își gestionează memoria. Să aruncăm o privire la modul de prevenire a scurgerilor de memorie în închiderile rapide.

numărarea automată a referințelor

în iOS, memoria partajată este gestionată prin faptul că fiecare obiect ține evidența numărului de alte obiecte care au o referință la acesta. Odată ce acest număr de referință ajunge la 0, ceea ce înseamnă că nu mai există referințe la obiect, acesta poate fi șters în siguranță din memorie. După cum sugerează și numele, această numărare a referințelor este necesară numai pentru tipurile de referință, în timp ce tipurile de valori nu necesită gestionarea memoriei. Numărarea referințelor s-a făcut manual în trecut apelând retain pe un obiect pentru a-și crește numărul de referință și apoi apelând release pe obiect pentru a-și reduce numărul de referință. Acest cod a fost aproape în întregime placa de cazan, plictisitor pentru a scrie și greșeală predispuse. Deci, la fel ca majoritatea sarcinilor umile, a devenit automatizat prin numărarea automată a referințelor (ARC), ceea ce face ca apelurile necesare să păstreze și să elibereze în numele dvs. la momentul compilării.

chiar dacă ARC a scăzut în cea mai mare parte nevoia de a vă face griji cu privire la gestionarea memoriei, poate crea totuși scurgeri de memorie Swift ori de câte ori există referințe circulare. De exemplu, spuneți că avem o clasă Person care are o proprietate pentru o apartment și clasa Apartment are o proprietate Person numită 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

când creăm un nou Person și Apartment și le atribuim proprietăților celorlalți, acum se referă reciproc într-un mod circular. Referințele circulare se pot întâmpla și cu mai mult de două obiecte. În această situație, atât Person, cât și Apartment dețin o referință la celălalt, deci în acest caz niciunul nu va ajunge vreodată la un număr de referință de 0 și se va ține reciproc în memorie, chiar dacă niciun alt obiect nu are referințe la niciunul dintre ele. Acest lucru este cunoscut ca un ciclu de reținere și provoacă o scurgere de memorie.

Related: trei caracteristici care ar fi Real Swifty

modul în care ARC se ocupă cu cicluri de reținere este de a avea diferite tipuri de referințe: puternic, slab și unowned. Referințele puternice sunt cele despre care am vorbit deja, iar aceste referințe măresc numărul de referințe al unui obiect. Referințe slabe, pe de altă parte, în timp ce încă oferindu-vă referință la obiect, nu crește numărul de referință. Deci, dacă ar fi să luăm proprietatea tenant pe Apartment și să o facem o referință slabă în schimb, ar rupe ciclul nostru de reținere:

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

acum, Obiectul nostru Person își păstrează încă Apartment în memorie, dar inversul nu mai este adevărat. Deci, atunci când ultima referință puternică la Person a dispărut, numărul său de referință va scădea la 0 și va elibera Apartment, al cărui număr de referință va fi, de asemenea, scăzut la 0 și ambele pot fi eliminate corect din memorie.

în Swift, referințele slabe trebuie să fie opționale var s, deoarece, dacă nu vă asumați responsabilitatea pentru păstrarea unui obiect în memorie, nu puteți garanta că obiectul nu se va schimba sau nu va părăsi memoria. Aici intră în joc al treilea tip de referință. referințele nedeținute sunt ca referințele slabe, cu excepția faptului că pot fi non-opționale lets, dar acestea ar trebui utilizate numai atunci când sunteți sigur că obiectul nu ar trebui să fie niciodată nil. La fel ca Force unwrapped optionals, îi spuneți compilatorului „nu vă faceți griji, am primit asta. Crede-mă.”Dar, la fel ca referințele slabe, referința nedeținută nu face nimic pentru a păstra obiectul în memorie și dacă lasă memoria și încercați să îl accesați, aplicația dvs. se va prăbuși. Din nou, la fel ca forța opționalele despachetate.

în timp ce ciclurile de reținere sunt ușor de văzut cu două obiecte îndreptate unul spre celălalt, ele sunt mai greu de văzut când sunt implicate închideri în Swift și aici am văzut că se întâmplă cele mai multe cicluri de reținere.

evitarea ciclurilor de reținere în închideri

este important să ne amintim că închiderile sunt tipuri de referință în Swift și pot provoca cicluri de reținere la fel de ușor, dacă nu mai ușor, ca clasele. Pentru ca o închidere să se execute mai târziu, trebuie să păstreze orice variabile de care are nevoie pentru a rula. În mod similar claselor, o închidere surprinde referințele ca fiind puternice în mod implicit. Un ciclu reține cu o închidere ar arata ceva de genul asta:

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

Related: când ar trebui să folosesc blocuri și închideri sau delegați pentru apeluri?

nou apel la acțiune

în acest caz, clasa SomeObject are o referință puternică la aClosure și aClosure a capturat self (instanța SomeObject) puternic, de asemenea. Acesta este motivul pentru care Swift vă face întotdeauna să adăugați self. în mod explicit în timp ce vă aflați într-o închidere pentru a împiedica programatorii să captureze accidental self fără să-și dea seama și, prin urmare, cel mai probabil provoacă un ciclu de reținere.

pentru a avea o variabile de captare de închidere ca slab sau unowned, puteți da instrucțiunile de închidere cu privire la modul de a captura anumite variabile:

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

mai multe despre sintaxa de închidere aici.

cele mai multe exemple de închidere pe care le-am văzut în tutoriale sau exemple par să captureze self ca unowned și numesc o zi, deoarece capturarea self ca slab face opțional, care poate fi mai incomod pentru a lucra cu. Dar, așa cum am aflat înainte, acest lucru este în mod inerent nesigur, deoarece va exista un accident dacă self nu mai este în memorie. Acest lucru nu este cu mult diferit de a forța doar despachetarea tuturor variabilelor opționale, deoarece nu doriți să faceți munca pentru a le desface în siguranță. Dacă nu puteți fi sigur că self va fi în jur atâta timp cât închiderea dvs. este, ar trebui să încercați să o capturați slab. Dacă aveți nevoie de un auto non-opțional în interiorul închiderii, luați în considerare utilizarea unui if let sau guard let pentru a obține un self puternic, non-opțional în interiorul închiderii. Deoarece ați făcut această referință nouă și puternică în interiorul închiderii rapide, nu veți crea un ciclu de reținere, deoarece această referință va fi lansată la sfârșitul închiderii:

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

sau chiar mai bine:

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

capturarea de sine puternic

deși este o bună practică pentru a captura self slab în închideri, nu este întotdeauna necesar. Închiderile care nu sunt reținute de self îl pot capta puternic fără a provoca un ciclu de reținere. Câteva exemple comune în acest sens sunt:

lucrul cu DispatchQueue s în GCD

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

lucrul cu UIView.animate(withDuration:)

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

primul nu este un ciclu de reținere, deoarece self nu reține DispatchQueue.main singleton. În al doilea exemplu, UIView.animate(withDuration:) este o metodă de clasă, care self nu are nici o parte în reținere.

capturarea self puternic în aceste situații nu va provoca un ciclu Reține, dar, de asemenea, nu poate fi ceea ce vrei. De exemplu, revenind la GCD:

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

această închidere nu va rula pentru încă 60 de secunde și va păstra self până când nu. Acesta poate fi comportamentul dorit, dar dacă doriți ca self să puteți lăsa memoria în acest timp, ar fi mai bine să o capturați slab și să rulați numai dacă self este încă în jur:

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

un alt loc interesant în care self nu ar trebui să fie capturat puternic este în lazy variabile, care nu sunt închideri, deoarece vor fi rulate o dată (sau niciodată) și apoi eliberate după aceea:

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

cu toate acestea, dacă o variabilă lazy este o închidere, ar trebui să se capteze slab pe sine. Un bun exemplu în acest sens vine din Ghidul limbajului de programare 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) />" } }}

acesta este, de asemenea, un bun exemplu de instanță care este rezonabilă pentru a utiliza o referință nedeținută, deoarece închiderea asHTML ar trebui să existe atât timp cât HTMLElement o face, dar nu mai este.

TL;DR

când lucrați cu închideri în Swift, fiți atenți la modul în care capturați variabile, în special self s. Dacă self păstrează închiderea în vreun fel, asigurați-vă că o capturați slab. Numai variabile de captare ca unowned atunci când puteți fi sigur că vor fi în memorie ori de câte ori închiderea este rulat, nu doar pentru că nu doriți să lucrați cu un opțional self. Acest lucru vă va ajuta să preveniți scurgerile de memorie în închideri rapide, ceea ce duce la o performanță mai bună a aplicației.

Lasă un răspuns

Adresa ta de email nu va fi publicată.