en minnesläcka uppstår i en iOS-app när minne som inte längre används inte rensas ordentligt och fortsätter att ta plats. Detta kan skada appens prestanda, och så småningom, när appen tar slut på tillgängligt minne, kommer det att orsaka en krasch. För att bättre förstå hur minnesläckor händer är det viktigt att först veta hur iOS-appar hanterar deras minne. Låt oss ta en titt på hur man förhindrar minnesläckor i snabba stängningar.

Automatisk referensräkning

i iOS hanteras delat minne genom att varje objekt håller reda på hur många andra objekt som har en referens till det. När denna referensräkning når 0, vilket betyder att det inte finns några fler referenser till objektet, kan det säkert rensas från minnet. Som namnet antyder är denna referensräkning endast nödvändig för referenstyper, medan värdetyper inte kräver någon minneshantering. Referensräkning gjordes manuellt tidigare genom att ringa retain på ett objekt för att öka referensräkningen och sedan ringa release på objektet för att minska referensräkningen. Denna kod var nästan helt pannplatta, tråkigt att skriva och misstag benägna. Så som de flesta simpla uppgifter blev det automatiserat genom automatisk referensräkning (ARC) som gör det nödvändigt att behålla och släppa samtal på dina vägnar vid kompileringstiden.

även om ARC för det mesta minskade behovet av att oroa sig för minneshantering, kan det fortfarande skapa snabba minnesläckor när det finns cirkulära referenser. Säg till exempel att vi har en Person – klass som har en egenskap för en apartment och Apartment – klassen har en Person – egenskap som heter 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 skapar en ny Person och Apartment och tilldelar dem till varandras egenskaper, refererar de nu till varandra på ett cirkulärt sätt. Cirkulära referenser kan också hända med mer än två objekt. I den här situationen håller både Person och Apartment en hänvisning till den andra, så i det här fallet kommer ingen av dem någonsin att komma ner till ett referensantal på 0 och kommer att hålla varandra i minnet, även om inga andra objekt har referenser till någon av dem. Detta är känt som en behålla cykel och orsakar en minnesläcka.

relaterad: tre funktioner som skulle vara riktiga Swifty

det sätt som ARC behandlar behålla cykler är att ha olika typer av referenser: stark, svag och oägda. Starka referenser är den typ vi redan har pratat om, och dessa referenser ökar ett objekts referensräkning. Svaga referenser, å andra sidan, medan du fortfarande ger dig hänvisning till objektet, öka inte referensräkningen. Så om vi skulle ta egenskapen tenantApartment och göra den till en svag referens istället, skulle den bryta vår behållningscykel:

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

nu håller vårt Person – objekt fortfarande sitt Apartment i minnet, men det omvända är inte längre sant. Så när den sista starka hänvisningen till Person är borta, kommer referensräkningen att sjunka till 0 och det kommer att släppa sin Apartment, vars referensräkning också kommer att släppas till 0, och de kan båda rensas korrekt från minnet.

i Swift måste svaga referenser vara valfria var s eftersom om du inte tar ansvar för att hålla ett objekt i minnet kan du inte garantera att objektet inte ändras eller lämnar minnet. Det är här den tredje typen av referens spelar in. oanvända referenser är som svaga referenser, förutom att de kan vara icke-valfria lets, men dessa bör endast användas när du är säker på att objektet aldrig ska vara nil. Som force unwrapped optionals, säger du kompilatorn ” oroa dig inte, jag har det här. Lita på mig.”Men som svaga referenser gör den oanvända referensen ingenting för att hålla objektet i minnet och om det lämnar minnet och du försöker komma åt det, kommer din app att krascha. Återigen, precis som force unwrapped optionals.

medan behållningscykler är lätta att se med två objekt som pekar på varandra, är de svårare att se när stängningar i Swift är inblandade, och det är här Jag har sett de flesta behållningscykler hända.

undvika behålla cykler i förslutningar

det är viktigt att komma ihåg att förslutningar är referenstyper i Swift och kan orsaka behålla cykler lika lätt, om inte lättare, som klasser. För att en stängning ska kunna köras senare måste den behålla alla variabler som den behöver för att den ska kunna köras. På samma sätt som klasser fångar en stängning referenser som starka som standard. En behålla cykel med en stängning skulle se ut så här:

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

När ska jag använda block och stängningar eller delegater för återuppringning?

Ny uppmaning till handling

i detta fall har SomeObject-klassen en stark hänvisning till aClosure och aClosure har fångat self (SomeObject-instansen) också starkt. Det är därför Swift alltid gör att du lägger till self. uttryckligen i en stängning för att förhindra att programmerare oavsiktligt fångar self utan att inse det, och därför sannolikt orsakar en behållningscykel.

för att ha en stängning fånga variabler som svag eller oäkta, kan du ge stängnings instruktioner om hur man kan fånga vissa variabler:

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

mer om stängning syntax här.

de flesta stängningsexempel som jag har sett i handledning eller exempel verkar fånga self som unowned och kalla det en dag, eftersom fånga self som svag gör det valfritt, vilket kan vara mer obekvämt att arbeta med. Men som vi lärde oss tidigare är detta i sig osäkert eftersom det kommer att bli en krasch om self inte längre finns i minnet. Det här är inte mycket annorlunda än att bara tvinga upp alla dina valfria variabler eftersom du inte vill göra jobbet för att säkert packa upp dem. Om du inte kan vara säker på att self kommer att vara så länge som din stängning är, bör du försöka fånga den svag istället. Om du behöver ett icke-valfritt själv inuti din stängning, överväg att använda en if let eller guard let för att få en stark, icke-valfri self inuti Stängningen. Eftersom du gjorde denna nya, stark referens inuti Swift stängning, du kommer inte att skapa en behålla cykel eftersom denna referens kommer att släppas i slutet av stängningen:

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

eller ännu bättre:

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

fånga själv starkt

även om det är god praxis att fånga self svagt i nedläggningar, är det inte alltid nödvändigt. Stängningar som inte behålls av self kan fånga det starkt utan att orsaka en behållningscykel. Några vanliga exempel på detta är:

arbeta med DispatchQueue s i GCD

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

arbeta med UIView.animate(withDuration:)

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

den första är inte en behållningscykel eftersom self inte behåller DispatchQueue.main singleton. I det andra exemplet är UIView.animate(withDuration:) en klassmetod, som self inte heller har någon del i att behålla.

fånga self starkt i dessa situationer kommer inte att orsaka en behålla cykel, men det kan också inte vara vad du vill. Till exempel, gå tillbaka till GCD:

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

denna stängning kommer inte att köras i ytterligare 60 sekunder och kommer att behålla self tills den gör det. Det här kan vara det beteende du vill ha, men om du ville ha self för att kunna lämna minnet under den här tiden, skulle det vara bättre att fånga det svagt och bara springa om self fortfarande finns kvar:

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

en annan intressant plats där self inte skulle behöva fångas starkt är i lazy variabler, som inte är stängningar, eftersom de kommer att köras en gång (eller aldrig) och sedan släppas efteråt:

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

men om en lazy variabel är en stängning, skulle den behöva fånga sig själv svagt. Ett bra exempel på detta kommer från 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) />" } }}

Detta är också ett bra exempel på en instans som är rimlig att använda en obestämd referens, eftersom asHTML – Stängningen borde existera så länge som HTMLElement gör, men inte längre.

TL; DR

när du arbetar med stängningar i Swift, var uppmärksam på hur du fångar variabler, särskilt selfs. om self behåller Stängningen på något sätt, se till att fånga den svagt. Fånga bara variabler som oanvända när du kan vara säker på att de kommer att vara i minnet när Stängningen körs, inte bara för att du inte vill arbeta med en valfri self. Detta hjälper dig att förhindra minnesläckor i snabba stängningar, vilket leder till bättre appprestanda.

Lämna ett svar

Din e-postadress kommer inte publiceras.