k úniku paměti dochází v aplikaci iOS, když paměť, která se již nepoužívá, není řádně vymazána a nadále zabírá místo. To může poškodit výkon aplikace a nakonec, když aplikace vyčerpá dostupnou paměť, způsobí havárii. Abychom lépe porozuměli tomu, jak dochází k úniku paměti, je důležité nejprve vědět, jak aplikace iOS spravují svou paměť. Pojďme se podívat na to, jak zabránit úniku paměti v rychlých uzávěrech.

automatické počítání referencí

v systému iOS je sdílená paměť spravována tím, že každý objekt sleduje, kolik dalších objektů na ni má odkaz. Jakmile tento počet referencí dosáhne 0, což znamená, že na objekt již nejsou žádné odkazy, lze jej bezpečně vymazat z paměti. Jak název napovídá, toto počítání referencí je nutné pouze pro referenční typy, zatímco typy hodnot nevyžadují žádnou správu paměti. Počítání referencí bylo provedeno ručně v minulosti voláním retain na objekt, aby se zvýšil jeho referenční počet, a poté voláním release na objekt, aby se snížil jeho referenční počet. Tento kód byl téměř úplně kotel deska, nuda psát a chyba náchylné. Stejně jako většina podřadných úkolů se stala automatizovanou pomocí automatického počítání referencí (ARC), díky čemuž je nutné uchovávat a uvolňovat hovory vaším jménem v době kompilace.

přestože ARC většinou snížil potřebu starat se o správu paměti, může stále vytvářet Rychlé úniky paměti, kdykoli existují kruhové odkazy. Řekněme například, že máme třídu Person, která má vlastnost pro apartment a třída Apartment má vlastnost 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

když vytvoříme nové Person a Apartment a přiřadíme je k vlastnostem ostatních, nyní se navzájem odkazují kruhovým způsobem. Kruhové odkazy se mohou vyskytnout také u více než dvou objektů. V této situaci drží Person i Apartment odkaz na druhou, takže v tomto případě se ani jeden nedostane na referenční počet 0 a bude se držet v paměti, i když žádné jiné objekty nemají odkazy na žádný z nich. Toto je známé jako retain cyklus a způsobuje únik paměti.

související: tři funkce, které by byly skutečné Swifty

způsob, jakým se ARC zabývá retain cykly, je mít různé typy odkazů: silné, slabé a neznámé. Silné odkazy jsou takové, o kterých jsme již mluvili, a tyto odkazy zvyšují počet referencí objektu. Slabé odkazy, na druhou stranu, zatímco vám stále dávají odkaz na objekt, nezvyšujte jeho referenční počet. Takže pokud bychom měli vzít vlastnost tenant na Apartment a místo toho z ní udělat slabou referenci, přerušilo by to náš retain cyklus:

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

nyní náš objekt Person stále drží svůj Apartment v paměti, ale opak již není pravdivý. Takže když je poslední silný odkaz na Person pryč, jeho referenční počet klesne na 0 a uvolní jeho Apartment, jehož referenční počet bude také snížen na 0, a oba mohou být správně vymazány z paměti.

v systému Swift musí být slabé odkazy volitelné var s, protože pokud nepřebíráte odpovědnost za uchování objektu v paměti, nemůžete zaručit, že se objekt nezmění nebo neopustí paměť. Zde přichází do hry třetí typ odkazu. neznámé odkazy jsou jako slabé odkazy, kromě toho, že mohou být nepovinná let s, ale ty by měly být použity pouze tehdy, pokud jste si jisti, že objekt by nikdy neměl být nil. Stejně jako force unbrapped optionals, říkáte kompilátoru “ nebojte se, mám to. Věř mi.“Ale stejně jako slabé odkazy, neznámý odkaz nedělá nic, aby udržel objekt v paměti, a pokud opustí paměť a pokusíte se k němu přistupovat, vaše aplikace se zhroutí. Opět, stejně jako síly rozbalené optionals.

zatímco retain cykly jsou snadno vidět se dvěma objekty směřujícími na sebe, jsou těžší vidět, když se jedná o uzávěry v Swift, a to je místo, kde jsem viděl, že většina retain cyklů se stane.

vyhnout se udržet cykly v uzávěry

je důležité si uvědomit, že uzávěry jsou referenční typy v Swift a může způsobit udržet cykly stejně snadno, ne-li snadněji, jako třídy. Aby se uzavření mohlo spustit později, musí si zachovat všechny proměnné, které potřebuje ke spuštění. Podobně jako třídy, uzávěr zachycuje odkazy jako silné ve výchozím nastavení. Retenční cyklus s uzávěrem by vypadal asi takto:

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

související: kdy mám použít bloky a uzávěry nebo delegáty pro zpětné volání?

nová výzva k akci

v tomto případě má třída SomeObject silný odkaz na aClosure a aClosure také silně zachytil self (instance SomeObject). To je důvod, proč Swift vždy dělá přidat self. explicitně, zatímco v uzávěru, aby pomohl zabránit programátorům v náhodném zachycení self, aniž by si to uvědomili, a proto s největší pravděpodobností způsobí retain cyklus.

Chcete-li mít proměnné zachycení uzávěru jako slabé nebo neznámé, můžete zadat pokyny k uzavření, jak zachytit určité proměnné:

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

více o syntaxi uzavření zde.

zdá se, že většina příkladů uzavření, které jsem viděl v tutoriálech nebo příkladech, zachycuje self jako neznámé a nazývá to den, protože zachycení self jako slabé je volitelné, což může být pro práci nepohodlnější. Ale jak jsme se dozvěděli dříve, je to ze své podstaty nebezpečné, protože pokud self již nebude v paměti, dojde k havárii. To není moc odlišné, než jen vynutit rozbalení všech vašich volitelných proměnných, protože nechcete dělat práci, abyste je bezpečně rozbalili. Pokud si nemůžete být jisti, že self bude kolem, pokud je vaše uzavření, měli byste se místo toho pokusit zachytit slabé. Pokud potřebujete nepovinný self uvnitř uzávěru, zvažte použití if let nebo guard let k získání silného, nepovinného self uvnitř uzávěru. Protože jste udělali tento nový, silný odkaz uvnitř rychlého uzavření, nebudete vytvářet retain cyklus, protože tento odkaz bude uvolněn na konci uzavření:

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

nebo ještě lépe:

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

zachycení sebe silně

ačkoli je dobrým zvykem zachytit self slabě v uzávěrech, není vždy nutné. Uzávěry, které nejsou zadrženy self, mohou silně zachytit, aniž by způsobily retenční cyklus. Několik běžných příkladů je:

práce s DispatchQueue s v GCD

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

práce s UIView.animate(withDuration:)

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

první z nich není retain cyklus, protože self nezachovává DispatchQueue.main singleton. Ve druhém příkladu je UIView.animate(withDuration:) třídní metoda, která self také nemá žádnou roli při zachování.

zachycení self silně v těchto situacích nezpůsobí retain cyklus, ale také nemusí být to, co chcete. Například návrat na GCD:

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

toto uzavření nebude běžet po dobu dalších 60 sekund a zůstane self, dokud se tak nestane. Toto může být chování, které chcete, ale pokud jste chtěli self, abyste mohli během této doby opustit paměť, bylo by lepší ji zachytit slabě a spustit pouze tehdy, pokud je self stále kolem:

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

dalším zajímavým místem, kde by self nemuselo být silně zachyceno, je lazy proměnné, které nejsou uzávěry, protože budou spuštěny jednou (nebo nikdy) a poté uvolněny:

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

pokud je však proměnná lazy uzávěrem, musela by se zachytit slabě. Dobrým příkladem toho je průvodce programovacím jazykem Swift:

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

Toto je také dobrý příklad instance, která je rozumná pro použití neznámého odkazu, protože uzavření asHTML by mělo existovat tak dlouho, dokud HTMLElement, ale již ne.

TL; DR

při práci s uzávěry ve Swiftu dbejte na to, jak zachycujete proměnné, zejména self s. Pokud self drží uzávěr jakýmkoli způsobem, ujistěte se, že jej zachytíte slabě. Proměnné zachycujte pouze jako neznámé, pokud si můžete být jisti, že budou v paměti při každém spuštění uzávěru, nejen proto, že nechcete pracovat s volitelným self. To vám pomůže zabránit úniku paměti při rychlých uzávěrech, což vede k lepšímu výkonu aplikace.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.