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 tenant
på Apartment
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 var
s 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?
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 self
s. 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.