muistivuoto tapahtuu iOS-sovelluksessa, Kun muistia, joka ei ole enää käytössä, ei tyhjennetä kunnolla ja vie edelleen tilaa. Tämä voi vahingoittaa sovelluksen suorituskykyä, ja lopulta, kun sovelluksesta loppuu käytettävissä oleva muisti, aiheuttaa kaatumisen. Jotta ymmärtäisimme paremmin, miten muistivuodot tapahtuvat, on tärkeää ensin tietää, miten iOS-Sovellukset hallinnoivat muistiaan. Katsotaanpa, miten estää muistivuodot nopeissa sulkemisissa.

Automaattinen Viitelaskenta

iOS: ssä jaettua muistia hallitaan siten, että jokainen objekti pitää kirjaa siitä, kuinka monessa muussa objektissa on viittaus siihen. Kun tämä viiteluku saavuttaa arvon 0, eli esineeseen ei ole enää viittauksia, se voidaan poistaa turvallisesti ulkomuistista. Kuten nimestä voisi päätellä, tämä viitelaskenta on tarpeen vain viitetyypeille, kun taas arvotyypit eivät vaadi muistinhallintaa. Viitelaskenta tehtiin aiemmin manuaalisesti kutsumalla retain kohdetta lisäämään sen viitelukua ja sitten kutsumalla release kohdetta pienentämään sen viitelukua. Tämä koodi oli lähes kokonaan kattilan levy, tylsä kirjoittaa ja virhe altis. Joten kuten useimmat vähäpätöiset tehtävät, siitä tuli automatisoitu automaattisella Referenssilaskennalla (ARC), joka tekee tarvittavat säilyttää ja vapauttaa puhelut puolestasi käännösaikaan.

vaikka ARC lähinnä vähensi tarvetta murehtia muistinhallinnasta, se voi silti luoda nopeita muistivuotoja aina, kun on ympyräviittauksia. Esimerkiksi sanotaan, että meillä on Person luokka, jolla on ominaisuus apartment ja Apartment luokalla on Person ominaisuus nimeltä 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

kun luomme uuden Person ja Apartment ja osoitamme ne toistensa ominaisuuksiksi, ne viittaavat nyt toisiinsa ympyränmuotoisesti. Kehäviittauksia voi tapahtua myös useammalla kuin kahdella esineellä. Tässä tilanteessa sekä Person että Apartment pitävät yllä viittausta toiseen, joten tässä tapauksessa kumpikaan ei koskaan laske referenssilukua 0 ja pitää toisiaan muistissa, vaikka muissa olioissa ei ole viittauksia kumpaankaan. Tätä kutsutaan säilymisjaksoksi ja se aiheuttaa muistivuodon.

Related: Three features That ’ d be Real Swifty

the way that ARC treats with retain cycles is to have different types of references: strong, weak and unowned. Vahvat viittaukset ovat sellaisia, joista olemme jo puhuneet, ja nämä viittaukset lisäävät kohteen viitemäärää. Heikot viittaukset, toisaalta, vaikka edelleen antaa viittauksen objektiin, eivät lisää sen referenssilukua. Jos siis ottaisimme tenant: n ominaisuuden Apartment ja tekisimme siitä sen sijaan heikon viitteen, se katkaisisi meidän säilymisjaksomme:

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

nyt Person kohteemme pitää edelleen Apartment muistissaan, mutta päinvastainen ei enää pidä paikkaansa. Kun siis viimeinen vahva viittaus Person on poissa, sen viiteluku putoaa 0: een ja se vapauttaa Apartment: n, jonka viiteluku myös putoaa 0: een, ja ne molemmat voidaan tyhjentää oikein muistista.

Swiftissä heikkojen viittausten tulee olla valinnaisia vars, koska jos et ota vastuuta esineen säilyttämisestä muistissa, et voi taata, ettei esine muutu tai poistu muistista. Tähän liittyy kolmas viittaustyyppi. unowned-viittaukset ovat kuin heikkoja viittauksia, paitsi että ne voivat olla ei-valinnaisia lets, mutta näitä tulee käyttää vain silloin, kun on varma, että objekti ei saisi koskaan olla nil. Kuten force unwrapped optionals, kerrot kääntäjälle ” älä huoli, minä hoidan tämän. Luota minuun.”Mutta kuten heikot viittaukset, unowned viittaus ei tee mitään pitää objektin muistissa ja jos se jättää muistin ja yrität käyttää sitä, sovellus kaatuu. Kuten pakkoruotsittomat optionalitkin.

vaikka säilymisjaksoja on helppo nähdä kahden toisiaan osoittavan kohteen kanssa, niitä on vaikeampi nähdä, kun Swiftin sulkemiset ovat mukana, ja tässä olen nähnyt useimpien säilymisjaksojen tapahtuvan.

Säilymisjaksojen välttäminen sulkemisissa

on tärkeää muistaa, että sulkemiset ovat Swift-järjestelmän viitetyyppejä ja voivat aiheuttaa säilymisjaksoja yhtä helposti, ellei jopa helpommin kuin luokat. Jotta sulkeminen voidaan suorittaa myöhemmin, sen on säilytettävä kaikki muuttujat, joita se tarvitsee suorittaakseen sen. Samoin kuin luokissa, sulkeminen tallentaa viittaukset oletusarvoisesti vahvoiksi. Säilyttämiskierros, jossa on sulkeminen, näyttäisi jotakuinkin tältä.:

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

liittyvä: Milloin minun pitäisi käyttää lohkojen ja sulkemiset tai edustajat Takaisinsoittoja?

 New call-to-action

tässä tapauksessa SomeObject – luokassa on vahva viittaus aClosure ja aClosure on kaapannut self (SomeObject – tapaus) vahvasti myös. Tämän vuoksi Swift laittaa aina lisäämään self. eksplisiittisesti sulkemisvaiheessa estääkseen ohjelmoijia vahingossa kaappaamasta self huomaamattaan ja siten todennäköisesti aiheuttamasta säilyttämissykliä.

jos sulkemismuuttujat ovat heikkoja tai niitä ei ole, voit antaa sulkemisohjeet tiettyjen muuttujien kaappaamiseen:

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

lisää sulkemissyntaksista täällä.

useimmat tutorialsissa tai esimerkeissä näkemäni sulkemisesimerkit näyttävät kaappaavan self unownediksi ja kutsuvat sitä päiväksi, koska self kaappaaminen heikkona tekee siitä valinnaisen, mikä voi olla hankalampaa työskennellä. Mutta kuten aiemmin opimme, tämä on luonnostaan vaarallista, koska tulee kaatuminen, jos self ei ole enää muistissa. Tämä ei ole paljon erilainen kuin vain pakottaa käärimistä kaikki valinnaiset muuttujat, koska et halua tehdä työtä turvallisesti avata ne. Ellet voi olla varma, että self on olemassa niin kauan kuin sulkemisesi on, kannattaa sen sijaan yrittää vangita heikko. Jos tarvitset ei-valinnaisen itsensä sulkemisesi sisään, harkitse if let: n tai guard let: n käyttämistä saadaksesi vahvan, ei-valinnaisen self: n sulkimen sisään. Koska olet tehnyt tämän uuden, vahva viittaus sisällä Swift sulkeminen, et luo säilyttää sykli, koska tämä viittaus julkaistaan lopussa sulkeminen:

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

tai vielä parempi:

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

itsensä vangitseminen voimakkaasti

vaikka on hyvä käytäntö pyydystää self heikosti sulkutilanteissa, se ei ole aina tarpeen. Sulkimet, jotka eivät säily self: llä, voivat vangita sen voimakkaasti aiheuttamatta pidätyskiertoa. Muutamia yleisiä esimerkkejä tästä ovat:

työskentely DispatchQueues: n kanssa GCD: ssä

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

työskentely UIView.animate(withDuration:)

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

ensimmäinen ei ole säilymisjakso, sillä self ei säilytä DispatchQueue.main singletonia. Toisessa esimerkissä UIView.animate(withDuration:) on luokkamenetelmä, jolla self ei myöskään ole osaa säilyttää.

kaappaaminen self voimakkaasti näissä tilanteissa ei aiheuta pidätyskierrettä, mutta se ei myöskään välttämättä ole sitä, mitä haluat. Esimerkiksi paluu GCD: hen:

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

tämä sulku kestää enää 60 sekuntia ja säilyttää self siihen asti. Tämä voi olla haluamasi käytös, mutta jos haluat self kykenevän jättämään muistin tänä aikana, olisi parempi vangita se heikosti ja juosta vain, jos self on vielä noin:

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

toinen mielenkiintoinen paikka, jossa self ei tarvitsisi ottaa vahvasti kiinni, on lazy muuttujissa, jotka eivät ole sulkuja, koska ne ajetaan kerran (tai ei koskaan) ja vapautetaan jälkeenpäin:

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

jos kuitenkin lazy muuttuja on sulkeuma, sen pitäisi kaapata itsensä heikosti. Hyvä esimerkki tästä on 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) />" } }}

tämä on myös hyvä esimerkki instanssista, joka on järkevä käyttää unowned-viittausta, sillä asHTML sulkemisen pitäisi olla olemassa niin kauan kuin HTMLElement on, mutta ei enää.

TL; DR

kun käsittelet sulkemisia Swiftissä, muista ottaa huomioon, miten tallennat muuttujat, erityisesti selfs. jos self säilyttää sulkemisen jollakin tavalla, muista tallentaa se heikosti. Ota muuttujat haltuun vain, kun voit olla varma, että ne ovat muistissa aina, kun sulkemista suoritetaan, ei vain siksi, ettet halua käyttää valinnaista self. Tämä auttaa ehkäisemään muistivuotoja nopeissa sulkemisissa, mikä parantaa sovelluksen suorituskykyä.

Vastaa

Sähköpostiosoitettasi ei julkaista.