memóriaszivárgás akkor fordul elő egy iOS-alkalmazásban, amikor a már nem használt memória nem törlődik megfelelően, és továbbra is helyet foglal. Ez károsíthatja az alkalmazás teljesítményét, és végül, amikor az alkalmazás elfogy a rendelkezésre álló memóriából, összeomlást okoz. Annak érdekében, hogy jobban megértsük, hogyan történik a memóriaszivárgás, fontos először tudni, hogy az iOS-Alkalmazások hogyan kezelik a memóriájukat. Vessünk egy pillantást arra, hogyan lehet megakadályozni a memória szivárgását a gyors bezárások során.

automatikus Referenciaszámlálás

az iOS rendszerben a megosztott memóriát úgy kezelik, hogy minden objektum nyomon követi, hogy hány más objektum hivatkozik rá. Amint ez a referenciaszám eléri a 0-t, vagyis nincs több hivatkozás az objektumra, biztonságosan törölhető a memóriából. Ahogy a neve is sugallja, ez a referenciaszámlálás csak referenciatípusokhoz szükséges, míg az értéktípusok nem igényelnek memóriakezelést. A referenciaszámlálást a múltban manuálisan hajtották végre úgy, hogy egy objektumon retain – et hívtak meg a referenciaszám növelése érdekében, majd az objektumon release – ot hívtak meg a referenciaszám csökkentése érdekében. Ez a kód szinte teljes egészében kazánlemez volt, unalmas írni és hibára hajlamos. Tehát, mint a legtöbb alantas feladat, automatizálttá vált az automatikus Referenciaszámlálás (ARC) révén, amely fordítási időben elvégzi a szükséges megtartási és kiadási hívásokat az Ön nevében.

annak ellenére, hogy az ARC többnyire csökkentette a memóriakezeléssel kapcsolatos aggodalom szükségességét, mégis gyors memóriaszivárgást okozhat, ha körkörös hivatkozások vannak. Tegyük fel például, hogy van egy Person osztályunk, amely rendelkezik egy apartment tulajdonsággal, a Apartment osztálynak pedig egy Person tulajdonságatenant:

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

amikor létrehozunk egy új Person – et és Apartment – ot, és hozzárendeljük őket egymás tulajdonságaihoz, akkor most körkörös módon hivatkoznak egymásra. A körkörös hivatkozások kettőnél több objektummal is előfordulhatnak. Ebben a helyzetben mind az Person, mind a Apartment hivatkozást tart a másikra, így ebben az esetben egyik sem fog soha 0 referenciaszám alá esni, és megtartják egymást a memóriában, annak ellenére, hogy más objektumoknak nincs hivatkozása egyikre sem. Ezt nevezik retain ciklusnak, és memóriaszivárgást okoz.

Related: három olyan funkció, amely valódi Swifty lenne

az ARC a megtartási ciklusokkal foglalkozik, hogy különböző típusú referenciákkal rendelkezik: erős, gyenge és unowned. Az erős hivatkozások azok, amelyekről már beszéltünk, és ezek a hivatkozások növelik az objektum referenciaszámát. A gyenge referenciák viszont, miközben továbbra is hivatkoznak az objektumra, nem növelik annak referenciaszámát. Tehát, ha a tenant tulajdonságot Apartment – ra vesszük, és gyenge referenciává tesszük, akkor megszakítja a megtartási ciklust:

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

most a Person objektum még mindig tartja a Apartment a memóriában, de a fordított már nem igaz. Tehát amikor az utolsó erős hivatkozás a Person – re eltűnik, a referenciaszám 0-ra csökken, és felszabadítja a Apartment – ot, amelynek referenciaszáma szintén 0-ra csökken, és mindkettő helyesen törölhető a memóriából.

a Swift-ben a gyenge hivatkozásoknak opcionálisnak kell lenniük var s mert ha nem vállal felelősséget az objektum memóriában tartásáért, akkor nem garantálhatja, hogy az objektum nem változik vagy elhagyja a memóriát. Itt jön létre a harmadik típusú referencia. a nem birtokolt hivatkozások olyanok, mint a gyenge hivatkozások, azzal a különbséggel, hogy nem opcionálisak lehetnek lets, de ezeket csak akkor szabad használni, ha biztos benne, hogy az objektum soha nem lehet nil. Mint az erő kibontott opciói, azt mondod a fordítónak: “ne aggódj, ezt kaptam. Bízz bennem.”De a gyenge referenciákhoz hasonlóan a nem birtokolt referencia sem tesz semmit azért, hogy az objektumot a memóriában tartsa, és ha elhagyja a memóriát, és megpróbál hozzáférni, az alkalmazás összeomlik. Újra, csakúgy, mint az erő kibontott opciói.

míg a megtartási ciklusok könnyen láthatók, ha két objektum egymásra mutat,nehezebb látni őket, amikor a Swift-ben lezárások vannak, és itt láttam a legtöbb megtartási ciklust.

a ciklusok megtartásának elkerülése a lezárásokban

fontos megjegyezni, hogy a lezárások referenciatípusok a Swift-ben, és ugyanolyan könnyen, ha nem könnyebben megtarthatják a ciklusokat, mint az osztályok. Annak érdekében, hogy a lezárás később végrehajtható legyen, meg kell őriznie a futtatásához szükséges változókat. Az osztályokhoz hasonlóan a Bezárás alapértelmezés szerint erős referenciákat rögzít. A megtartása ciklus lezárása nézne valami ilyesmi:

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

mikor kell blokkokat és Bezárásokat vagy küldötteket használni a Visszahívásokhoz?

 új cselekvésre ösztönzés

ebben az esetben a SomeObject osztály erősen hivatkozik aClosure-re, és aClosure erősen rögzítette a self-t (a SomeObject példányt) is. Ez az oka annak, hogy a Swift mindig kifejezetten hozzáadja a self. – ot egy Bezárás közben, hogy megakadályozza a programozókat abban, hogy véletlenül rögzítsék a self – t anélkül, hogy észrevennék, és ezért valószínűleg megtartási ciklust okoznak.

ahhoz, hogy a zárás rögzítési változói gyengék vagy nem ismertek legyenek, megadhatja a zárási utasításokat bizonyos változók rögzítéséről:

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

További információ a bezárási szintaxisról itt.

a legtöbb lezárási példa, amelyet az oktatóanyagokban vagy példákban láttam, úgy tűnik, hogy self – et nem birtokoltként rögzíti, és napnak nevezi, mivel self gyenge rögzítése opcionálissá teszi, ami kényelmetlenebb lehet. De ahogy korábban megtanultuk, ez eleve nem biztonságos, mivel összeomlik, ha self már nincs a memóriában. Ez nem sokban különbözik attól, hogy csak az összes opcionális változót kicsomagolja, mert nem akarja elvégezni a munkát, hogy biztonságosan kicsomagolja őket. Hacsak nem biztos benne, hogy self körül lesz, amíg a lezárás, meg kell próbálnia, hogy rögzítse gyenge helyett. Ha szüksége van egy nem opcionális self belül a lezárás, fontolja meg egy if let vagy guard let, hogy egy erős, nem választható self belül a lezárás. Mivel ezt az új, erős hivatkozást a Swift bezáráson belül készítette, nem hoz létre megtartási ciklust, mivel ez a hivatkozás a Bezárás végén jelenik meg:

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

vagy még jobb:

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

erősen rögzítse önmagát

bár jó gyakorlat a self gyenge rögzítése zárásokban, ez nem mindig szükséges. Azok a lezárások, amelyeket a self nem tart meg, erősen megragadhatják anélkül, hogy megtartási ciklust okoznának. Néhány gyakori példa erre:

DispatchQueue S használata a GCD-ben

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

munka UIView.animate(withDuration:)

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

az első nem megtartási ciklus, mivel a self nem tartja meg a DispatchQueue.main szingulettet. A második példában a UIView.animate(withDuration:) egy osztály módszer, amelynek self szintén nincs része a megtartásban.

a self erőteljes rögzítése ezekben a helyzetekben nem okoz megtartási ciklust, de lehet, hogy nem is az, amit akar. Például visszatérve a GCD-hez:

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

ez a lezárás nem fut további 60 másodpercig, és megtartja self amíg meg nem történik. Ez lehet a kívánt viselkedés, de ha azt akarod, hogy self képes legyen elhagyni a memóriát ez idő alatt, jobb lenne gyengén rögzíteni, és csak akkor futni, ha self még mindig körül van:

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

egy másik érdekes hely, ahol a self – et nem kell erősen rögzíteni, a lazy változók, amelyek nem zárások, mivel egyszer (vagy soha) futtatják őket, majd utána elengedik:

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

Ha azonban egy lazy változó lezárás, akkor gyengén kell rögzítenie önmagát. Erre jó példa a Swift programozási nyelv útmutatója:

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

ez egy jó példa egy olyan példányra is, amely ésszerű egy nem birtokolt hivatkozás használata, mivel a asHTML bezárásnak mindaddig léteznie kell, amíg a HTMLElement létezik, de már nem.

TL;DR

amikor a Swiftben lezárásokkal dolgozik, vegye figyelembe, hogyan rögzíti a változókat, különösen a self s. ha a self bármilyen módon megtartja a lezárást, ügyeljen arra, hogy gyengén rögzítse. Csak akkor rögzítse a változókat nem tulajdonosként, ha biztos lehet benne, hogy a Bezárás futtatásakor a memóriában lesznek, nem csak azért, mert nem akar opcionális self – vel dolgozni. Ez segít megelőzni a memória szivárgását a gyors bezárások során, ami jobb alkalmazás teljesítményhez vezet.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.