een geheugenlek treedt op in een iOS-app wanneer geheugen dat niet meer in gebruik is niet goed wordt gewist en ruimte blijft innemen. Dit kan de prestaties van de app schaden en uiteindelijk, wanneer de app geen Beschikbaar geheugen meer heeft, zal het een crash veroorzaken. Om beter te begrijpen hoe geheugenlekken gebeuren, is het belangrijk om eerst te weten hoe iOS-apps hun geheugen beheren. Laten we eens kijken hoe je geheugenlekken in snelle sluitingen kunt voorkomen.

automatische referentietelling

in iOS wordt gedeeld geheugen beheerd door elk object bij te houden hoeveel andere objecten ernaar verwijzen. Zodra deze referentietelling 0 bereikt, wat betekent dat er geen verwijzingen meer zijn naar het object, kan het veilig uit het geheugen worden gewist. Zoals de naam zou impliceren, is deze referentietelling alleen nodig voor referentietypen, terwijl waardetypen geen geheugenbeheer vereisen. Referentietelling werd in het verleden handmatig gedaan door retain op een object aan te roepen om zijn referentietelling te verhogen en vervolgens release op het object aan te roepen om zijn referentietelling te verlagen. Deze code was bijna volledig ketelplaat, saai om te schrijven en fout gevoelig. Dus zoals de meeste ondergeschikte taken, het werd geautomatiseerd door middel van automatische referentie tellen (ARC) die de nodige te behouden en vrij te geven oproepen namens u bij het compileren maakt.

hoewel ARC meestal minder zorgen hoeft te maken over geheugenbeheer, kan het nog steeds snelle geheugenlekken creëren wanneer er cirkelreferenties zijn. Bijvoorbeeld, stel dat we een Person klasse hebben die een eigenschap heeft voor een apartment en de Apartment klasse heeft een Person eigenschap genaamd 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

wanneer we een nieuwe Person en Apartment maken en deze toewijzen aan elkaars eigenschappen, verwijzen ze nu op een cirkelvormige manier naar elkaar. Circulaire verwijzingen kunnen ook gebeuren met meer dan twee objecten. In deze situatie bevatten zowel de Person als de Apartment een verwijzing naar de andere, dus in dit geval zal geen van beide ooit een referentietelling van 0 krijgen en elkaar in het geheugen houden, ook al hebben geen andere objecten een verwijzing naar een van hen. Dit staat bekend als een retain-cyclus en veroorzaakt een geheugenlek.

gerelateerd: drie kenmerken die echt Swifty zouden zijn

de manier waarop ARC omgaat met retain cycles is om verschillende soorten referenties te hebben: sterk, zwak en niet-eigendom. Sterke referenties zijn het soort waar we het al over hebben gehad, en deze referenties verhogen het aantal referenties van een object. Zwakke referenties, aan de andere kant, terwijl ze je nog steeds verwijzen naar het object, verhogen de referentietelling niet. Dus als we de eigenschap tenant op Apartment zouden nemen en het in plaats daarvan een zwakke referentie zouden maken, zou het onze retain-cyclus breken:

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

nu houdt ons Person object nog steeds zijn Apartment in het geheugen, maar het omgekeerde is niet langer waar. Dus als de laatste sterke verwijzing naar de Person weg is, zal zijn referentietelling dalen naar 0 en het zal zijn Apartment vrijgeven, waarvan het referentietelling ook wordt gedaald naar 0, en ze kunnen beide correct worden gewist uit het geheugen.

in Swift, zwakke referenties moeten optioneel vars zijn, omdat, als u geen verantwoordelijkheid neemt voor het bewaren van een object in het geheugen, u niet kunt garanderen dat het object het geheugen niet zal veranderen of verlaten. Dit is waar het derde type referentie in het spel komt. referenties zonder eigendom zijn als zwakke referenties, behalve dat ze niet-optioneel lets kunnen zijn, maar deze moeten alleen worden gebruikt als u er zeker van bent dat het object nooit nilmag zijn. Zoals force unwrapped optionals, zeg je tegen de compiler ” maak je geen zorgen, ik regel dit. Vertrouw me.”Maar net als zwakke referenties, de unowned referentie is niets te doen om het object in het geheugen te houden en als het laat het geheugen en je probeert om toegang te krijgen, uw app zal crashen. Nogmaals, net als force unwrapped optionals.

hoewel bewaarcycli gemakkelijk te zien zijn met twee objecten die naar elkaar wijzen, zijn ze moeilijker te zien wanneer sluitingen in Swift betrokken zijn, en dit is waar ik de meeste bewaarcycli heb zien gebeuren.

vermijden Houdcycli in sluitingen

het is belangrijk te onthouden dat sluitingen referentietypen zijn in Swift en kan leiden tot houdcycli net zo gemakkelijk, zo niet gemakkelijker, als klassen. Om een afsluiting later uit te voeren, moet het alle variabelen behouden die het nodig heeft om het te laten draaien. Op dezelfde manier als klassen, een sluiting vangt referenties als sterk standaard. Een retain cyclus met een sluiting zou er ongeveer zo uitzien:

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

verwant: Wanneer moet Ik gebruik maken van blokken en sluitingen of Afgevaardigden voor Callbacks?

nieuwe call-to-action

in dit geval heeft de SomeObject klasse een sterke verwijzing naar aClosure en aClosure heeft self (de SomeObject instantie) ook sterk gevangen. Dit is de reden waarom Swift je altijd self. expliciet laat toevoegen terwijl je in een afsluiting zit om te voorkomen dat programmeurs per ongeluk self vastleggen zonder het te beseffen, en dus hoogstwaarschijnlijk een retain-cyclus veroorzaakt.

om closure capture-variabelen als zwak of niet-eigendom te hebben, kunt u de closure-instructies geven over het vastleggen van bepaalde variabelen:

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

meer over sluiting syntaxis hier.

de meeste afsluitingsvoorbeelden die ik in tutorials of voorbeelden heb gezien lijken self als unowned te vangen en noemen het een dag, omdat het vastleggen van self als zwak het optioneel maakt, wat meer lastig kan zijn om mee te werken. Maar zoals we eerder hebben geleerd, is dit inherent onveilig omdat er een crash zal zijn als self niet meer in het geheugen is. Dit is niet veel anders dan gewoon forceren uitpakken van al uw optionele variabelen, omdat je niet wilt om het werk te doen om ze veilig uit te pakken. Tenzij u er zeker van kunt zijn dat self rond zal zijn zolang uw afsluiting is, moet u proberen om het zwak te vangen in plaats daarvan. Als u een niet-optionele zelf in uw sluiting nodig hebt, overweeg dan een if let of guard let te gebruiken om een sterke, niet-optionele self in de sluiting te krijgen. Omdat je deze nieuwe, sterke referentie hebt gemaakt binnen de Swift closure, creëer je geen retain cycle omdat deze referentie aan het einde van de closure wordt vrijgegeven:

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

of nog beter:

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

het vangen van Self sterk

hoewel het een goede gewoonte is om self zwak te vangen in sluitingen, is het niet altijd nodig. Sluitingen die niet door de self worden vastgehouden, kunnen deze sterk opvangen zonder een retain-cyclus te veroorzaken. Enkele veel voorkomende voorbeelden hiervan zijn:

werken met DispatchQueues in GCD

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

werken met UIView.animate(withDuration:)

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

de eerste is geen bewaarcyclus omdat self de DispatchQueue.main singleton niet behoudt. In het tweede voorbeeld is UIView.animate(withDuration:) een klassemethode, die self ook geen rol speelt bij het behouden.

het vastleggen van self sterk in deze situaties zal geen retain-cyclus veroorzaken, maar het kan ook niet zijn wat u wilt. Bijvoorbeeld, teruggaan naar GCD:

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

deze sluiting zal niet langer duren dan 60 seconden en zal self behouden totdat dit gebeurt. Dit kan het gedrag zijn dat je wilt, maar als je wilt dat self in staat is om geheugen te verlaten gedurende deze tijd, zou het beter zijn om het zwak op te vangen en alleen te draaien als self nog steeds rond is:

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

een andere interessante plaats waar self niet sterk hoeft te worden vastgelegd is in lazy variabelen, die geen sluitingen zijn, omdat ze eenmaal (of nooit) worden uitgevoerd en daarna worden vrijgegeven:

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

als een lazy variabele echter een closure is, moet deze zichzelf zwak vastleggen. Een goed voorbeeld hiervan komt uit de Swift Programming Language 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) />" } }}

Dit is ook een goed voorbeeld van een instantie die redelijk is om een referentie zonder eigendom te gebruiken, aangezien de sluiting asHTML zou moeten bestaan zolang de sluiting HTMLElement bestaat, maar niet langer.

TL; DR

wanneer u met sluitingen in Swift werkt, moet u er rekening mee houden hoe u variabelen vastlegt, in het bijzonder selfs. als self de afsluiting op enigerlei wijze behoudt, zorg er dan voor dat u deze zwak opneemt. Capture variabelen alleen als unowned wanneer u er zeker van kunt zijn dat ze in het geheugen zullen zijn wanneer de afsluiting wordt uitgevoerd, niet alleen omdat u niet wilt werken met een optionele self. Dit zal u helpen geheugenlekken in snelle sluitingen te voorkomen, wat leidt tot betere app-prestaties.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.