der opstår en hukommelseslækage i en iOS-app, når hukommelse, der ikke længere er i brug, ikke ryddes korrekt og fortsætter med at tage plads. Dette kan skade appens ydeevne, og til sidst, når appen løber tør for tilgængelig hukommelse, vil det forårsage et nedbrud. For bedre at forstå, hvordan hukommelseslækager sker, er det vigtigt først at vide, hvordan iOS-apps administrerer deres hukommelse. Lad os se på, hvordan man forhindrer hukommelseslækager i hurtige lukninger.

automatisk Referencetælling

i iOS styres delt hukommelse ved at få hvert objekt til at holde styr på, hvor mange andre objekter der har en henvisning til det. Når dette referencetælling når 0, hvilket betyder, at der ikke er flere referencer til objektet, kan det ryddes sikkert fra hukommelsen. Som navnet antyder, er denne referencetælling kun nødvendig for referencetyper, mens værdityper ikke kræver hukommelsesstyring. Referencetælling blev tidligere udført manuelt ved at ringe retain på et objekt for at øge referencetællingen og derefter ringe release på objektet for at mindske referencetællingen. Denne kode var næsten udelukkende kedel plade, kedeligt at skrive og fejl tilbøjelige. Så som de fleste meniale opgaver blev det automatiseret gennem automatisk Referencetælling (ARC), som gør det nødvendigt at beholde og frigive opkald på dine vegne på kompileringstidspunktet.

selvom ARC for det meste mindskede behovet for at bekymre sig om hukommelsesstyring, kan det stadig skabe hurtige hukommelseslækager, når der er cirkulære referencer. Sig for eksempel, at vi har en Person klasse, der har en ejendom for en apartment, og Apartment klassen har en Person ejendom navngivet 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

når vi opretter en ny Person og Apartment og tildeler dem til hinandens egenskaber, henviser de nu til hinanden på en cirkulær måde. Cirkulære referencer kan også ske med mere end to objekter. I denne situation holder både Person og Apartment en henvisning til den anden, så i dette tilfælde vil ingen af dem nogensinde komme ned til et referenceantal på 0 og vil holde hinanden i hukommelsen, selvom ingen andre objekter har henvisninger til nogen af dem. Dette er kendt som en beholder cyklus og forårsager en hukommelseslækage.

relateret: tre funktioner, der ville være rigtig hurtige

den måde, ARC beskæftiger sig med behold-cyklusser, er at have forskellige typer referencer: stærk, svag og ikke-ejet. Stærke referencer er den slags, vi allerede har talt om, og disse referencer øger et objekts referencetælling. Svage referencer, på den anden side, mens du stadig giver dig henvisning til objektet, skal du ikke øge referencetællingen. Så hvis vi skulle tage tenant egenskaben på Apartment og gøre det til en svag reference i stedet, ville det bryde vores beholdningscyklus:

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

nu holder vores Person objekt stadig sin Apartment i hukommelsen, men det omvendte er ikke længere sandt. Så når den sidste stærke henvisning til Person er væk, falder dens referencetælling til 0, og den frigiver dens Apartment, hvis referencetælling også vil blive droppet til 0, og de kan begge ryddes korrekt fra hukommelsen.

i hurtige, svage referencer skal være valgfri var s, fordi hvis du ikke tager ansvar for at holde et objekt i hukommelsen, kan du ikke garantere, at objektet ikke ændrer eller forlader hukommelsen. Det er her den tredje type reference kommer i spil. ikke-ejede referencer er som svage referencer, bortset fra at de kan være ikke-valgfri lets, men disse bør kun bruges, når du er sikker på, at objektet aldrig skal være nil. Ligesom force uindpakkede optionals fortæller du kompilatoren ” bare rolig, jeg har det her. Stol på mig.”Men som svage referencer gør den ikke-ejede reference intet for at holde objektet i hukommelsen, og hvis det forlader hukommelsen, og du prøver at få adgang til det, vil din app gå ned. Igen, ligesom force uindpakkede optionals.

mens behold cykler er lette at se med to objekter, der peger på hinanden, er de sværere at se, når lukninger i hurtig er involveret, og det er her jeg har set de fleste behold cykler ske.

undgå Fastholdelsescyklusser i lukninger

det er vigtigt at huske, at lukninger er referencetyper i hurtig og kan forårsage fastholdelsescyklusser lige så let, hvis ikke lettere, som klasser. For at en lukning kan udføres senere, skal den beholde de variabler, den har brug for, for at den kan køre. På samme måde som klasser fanger en lukning referencer som stærke som standard. En behold cyklus med en lukning ville se noget som dette:

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

Hvornår skal jeg bruge blokke og lukninger eller delegerede til tilbagekald?

 ny opfordring til handling

i dette tilfælde har SomeObject klassen en stærk henvisning til aClosure og aClosure har også fanget self (SomeObject instansen) stærkt. Dette er grunden til, at hurtig altid får dig til at tilføje self. eksplicit, mens du er i en lukning for at forhindre, at programmører ved et uheld fanger self uden at indse det, og derfor sandsynligvis forårsager en tilbageholdelsescyklus.

for at få en lukningsoptagelsesvariabler som svage eller ikke-ejede, kan du give lukningsinstruktionerne om, hvordan du fanger visse variabler:

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

mere om lukning syntaks her.

de fleste lukningseksempler, som jeg har set i tutorials eller eksempler, ser ud til at fange self som ikke-ejet og kalde det en dag, da optagelse self som svag gør det valgfrit, hvilket kan være mere ubelejligt at arbejde med. Men som vi lærte før, er dette i sagens natur usikkert, da der vil være et nedbrud, hvis self ikke længere er i hukommelsen. Dette er ikke meget anderledes end bare at tvinge udpakning af alle dine valgfrie variabler, fordi du ikke ønsker at gøre arbejdet for sikkert at pakke dem ud. Medmindre du kan være sikker på, at self vil være så længe din Lukning er, skal du prøve at fange den svag i stedet. Hvis du har brug for et ikke-valgfrit selv inde i din Lukning, skal du overveje at bruge en if let eller guard let for at få en stærk, ikke-valgfri self inde i lukningen. Fordi du lavede denne nye, stærke reference inde i den hurtige lukning, du opretter ikke en fastholdelsescyklus, da denne reference frigives i slutningen af lukningen:

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

eller endnu bedre:

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

at fange selv stærkt

selvom det er god praksis at fange self svagt i lukninger, er det ikke altid nødvendigt. Lukninger, der ikke bevares af self, kan fange det stærkt uden at forårsage en tilbageholdelsescyklus. Et par almindelige eksempler på dette er:

arbejde med DispatchQueue s i GCD

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

arbejde med UIView.animate(withDuration:)

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

den første er ikke en behold cyklus siden self ikke bevarer DispatchQueue.main singleton. I det andet eksempel er UIView.animate(withDuration:) en klassemetode, som self heller ikke har nogen rolle i at bevare.

optagelse self stærkt i disse situationer vil ikke forårsage en tilbageholdelsescyklus, men det kan heller ikke være det, du vil have. For eksempel at gå tilbage til GCD:

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

denne lukning kører ikke i yderligere 60 sekunder og vil beholde self, indtil den gør det. Dette kan være den adfærd, du ønsker, men hvis du ville self for at kunne forlade hukommelsen i løbet af denne tid, ville det være bedre at fange det svagt og kun køre, hvis self stadig er omkring:

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

et andet interessant sted, hvor self ikke behøver at blive fanget stærkt, er i lazy variabler, der ikke er lukninger, da de vil blive kørt en gang (eller aldrig) og derefter frigivet bagefter:

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

men hvis en lazy variabel er en lukning, ville det være nødvendigt at fange selv svagt. Et godt eksempel på dette kommer fra den hurtige programmeringssprog guide:

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) />" } }}

dette er også et godt eksempel på en forekomst, der er rimelig at bruge en ikke-ejet reference, da lukningen asHTML skal eksistere, så længe HTMLElement gør, men ikke længere.

TL;DR

når du arbejder med lukninger i Hurtig, skal du være opmærksom på, hvordan du fanger variabler, især self s. hvis self bevarer lukningen på nogen måde, skal du sørge for at fange den svagt. Optag kun variabler som ikke-ejede, når du kan være sikker på, at de vil være i hukommelsen, når lukningen køres, ikke kun fordi du ikke vil arbejde med en valgfri self. Dette hjælper dig med at forhindre hukommelseslækager i hurtige lukninger, hvilket fører til bedre appydelse.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.