en minnelekkasje oppstår i en iOS-app når minne som ikke lenger er i bruk, ikke er riktig ryddet og fortsetter å ta opp plass. Dette kan skade appens ytelse, og til slutt, når appen går tom for tilgjengelig minne, vil det føre til et krasj. For å bedre forstå hvordan minnelekkasjer skje, er det viktig å først vite hvordan iOS apps administrere deres minne. La oss ta en titt på hvordan du kan forhindre minnelekkasjer i raske nedleggelser.

Automatisk Referansetelling

i iOS administreres delt minne ved at hvert objekt holder oversikt over hvor mange andre objekter som har en referanse til det. Når denne referansetellingen når 0, noe som betyr at det ikke er flere referanser til objektet, kan det trygt slettes fra minnet. Som navnet skulle tilsi, er denne referansetellingen bare nødvendig for referansetyper, mens verdityper krever ingen minnehåndtering. Referansetelling ble gjort manuelt tidligere ved å ringe retain på et objekt for å øke referanseantallet og deretter ringe release på objektet for å redusere referanseantallet. Denne koden var nesten helt kjele plate, kjedelig å skrive og feil utsatt. Så som de fleste menial oppgaver, det ble automatisert Gjennom Automatisk Referanse Telling (ARC) som gjør de nødvendige beholde og slipp samtaler på dine vegne på kompilere tid.

SELV OM ARC for det meste reduserte behovet for å bekymre seg for minnehåndtering, kan DET fortsatt skape Raske minnelekkasjer når det er sirkelreferanser. Si for eksempel at vi har en klasse Person som har en egenskap for en apartment og klassen Apartment har en egenskap kalt Person 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 oppretter en ny Person og Apartment og tilordner dem til hverandres egenskaper, refererer de nå til hverandre på en sirkulær måte. Sirkelreferanser kan også skje med mer enn to objekter. I denne situasjonen holder både Person og Apartment en referanse til den andre, så i dette tilfellet vil det aldri komme ned til et referansetall på 0 og vil holde hverandre i minnet, selv om ingen andre objekter har referanser til noen av dem. Dette er kjent som en beholder syklus og forårsaker en minnelekkasje.

Relatert: Tre funksjoner Som ville Være Ekte Swifty

MÅTEN ARC omhandler beholde sykluser er å ha forskjellige typer referanser: sterk, svak og unowned. Sterke referanser er den typen vi allerede har snakket om, og disse referansene øker et objekts referansetelling. Svake referanser, derimot, mens du fortsatt gir deg referanse til objektet, øker ikke referansetallet. Så hvis vi skulle ta egenskapen tenantApartment og gjøre den til en svak referanse i stedet, ville den bryte vår beholdningssyklus:

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

nå holder vårt Person objekt fortsatt Apartment i minnet, men omvendt er ikke lenger sant. Så når den siste sterke referansen til Person er borte, vil referansetallet falle til 0, og det vil frigjøre Apartment, hvis referansetelling vil også bli droppet til 0, og de kan begge slettes riktig fra minnet.

i Swift må svake referanser være valgfrie vars fordi hvis du ikke tar ansvar for å holde et objekt i minnet, kan du ikke garantere at objektet ikke vil endre eller forlate minnet. Det er her den tredje typen referanse kommer inn i spill. unowned referanser er som svake referanser, bortsett fra at de kan være ikke-valgfrie let s, men disse bør bare brukes når du er sikker på at objektet aldri skal være nil. Som force unwrapped optionals, forteller du kompilatoren «Ikke bekymre deg, jeg fikk dette. Stol på meg.»Men som svake referanser, gjør ikke den ukjente referansen noe for å holde objektet i minnet, og hvis det forlater minnet og du prøver å få tilgang til det, vil appen din krasje. Igjen, akkurat som force unwrapped optionals.

mens beholde sykluser er enkle å se med to objekter som peker på hverandre, er de vanskeligere å se når lukninger I Swift er involvert, og det er her jeg har sett de fleste beholde sykluser skje.

Unngå Å Beholde Sykluser I Lukninger

det er viktig å huske at lukninger er referansetyper I Swift og kan forårsake behold sykluser like enkelt, om ikke lettere, som klasser. For at en lukning skal kunne utføres senere, må den beholde alle variabler som den trenger for å kjøre. På samme måte som klasser fanger en lukning referanser som sterke som standard. A beholde syklusen med en lukning ville se noe ut som dette:

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

Related: Når Skal Jeg Bruke Blokker og Lukninger eller Delegater for Tilbakeringing?

 Ny oppfordring til handling

i dette tilfellet har SomeObject – klassen en sterk referanse til aClosure og aClosure har fanget self (SomeObject – forekomsten) også sterkt. Dette er grunnen Til At Swift alltid gjør at du legger til self. eksplisitt mens du er i en lukning for å forhindre at programmerere ved et uhell fanger self uten å innse det, og derfor sannsynligvis forårsaker en beholdningssyklus.

hvis du vil ha en lukning fange variabler som svak eller unowned, kan du gi lukning instruksjoner om hvordan å fange visse variabler:

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

Mer om nedleggelse syntaks her.

de fleste lukke eksempler som jeg har sett i tutorials eller eksempler synes å fange self som unowned og kaller det en dag, siden fange self som svak gjør det valgfritt, noe som kan være mer upraktisk å jobbe med. Men som vi lærte før, er dette iboende usikkert siden det vil bli et krasj hvis self ikke lenger er i minnet. Dette er ikke mye annerledes enn bare å tvinge utpakking av alle dine valgfrie variabler fordi du ikke vil gjøre arbeidet for å pakke dem trygt ut. Med mindre du kan være sikker på at self vil være rundt så lenge lukkingen din er, bør du prøve å fange den svak i stedet. Hvis du trenger et ikke-valgfritt selv inne i lukkingen, bør du vurdere å bruke en if let eller guard let for å få en sterk, ikke-valgfri self inne i lukkingen. Fordi du laget denne nye, sterke referansen inne I Swift-lukkingen, vil du ikke opprette en beholdningssyklus siden denne referansen vil bli utgitt på slutten av lukkingen:

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

eller enda bedre:

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

Fange Selv Sterkt

Selv om det er god praksis å fange self svakt i lukninger, er det ikke alltid nødvendig. Nedleggelser som ikke beholdes av self kan fange det sterkt uten å forårsake en beholdningssyklus. Noen vanlige eksempler på dette er:

Arbeide med DispatchQueue s I GCD

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

Arbeide med UIView.animate(withDuration:)

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

den forste er ikke a beholde syklusen siden self ikke beholder DispatchQueue.main singleton. I det andre eksemplet er UIView.animate(withDuration:) en klassemetode, som self heller ikke har noen rolle i å beholde.

Fange self sterkt i disse situasjonene vil ikke føre til en beholde syklus, men det kan heller ikke være hva du vil. For eksempel, gå tilbake TIL GCD:

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

denne nedleggelsen vil ikke løpe i ytterligere 60 sekunder og vil beholde self til den gjør det. Dette kan være oppførselen du vil ha, men hvis du ønsket self for å kunne forlate minnet i løpet av denne tiden, ville det være bedre å fange det svakt og bare kjøre hvis self fortsatt er rundt:

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

Et annet interessant sted hvor self ikke trenger å bli fanget sterkt, er i lazy variabler, som ikke er nedleggelser, siden de vil bli kjørt en gang (eller aldri) og deretter utgitt etterpå:

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

Men hvis en lazy variabel er en lukning, må den fange seg svakt. Et godt eksempel på Dette kommer Fra Swift Programmeringsspråk 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 som er rimelig å bruke en ukjent referanse, da asHTML – lukkingen skal eksistere så lenge HTMLElement gjør det, men ikke lenger.

TL; DR

når du arbeider med nedleggelser I Swift, vær oppmerksom på hvordan du fanger variabler, spesielt selfs. hvis self beholder lukkingen på noen måte, må du sørge for å fange den svakt. Bare fange variabler som unowned når du kan være sikker på at de vil være i minnet når lukkingen kjøres, ikke bare fordi du ikke vil jobbe med en valgfri self. Dette vil hjelpe deg med å forhindre minnelekkasjer I Raske nedleggelser, noe som fører til bedre appytelse.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.