um vazamento de memória ocorre em um aplicativo iOS quando a memória que não está mais em uso não está devidamente limpa e continua ocupando espaço. Isso pode prejudicar o desempenho do aplicativo e, eventualmente, quando o aplicativo ficar sem memória disponível, causará uma falha. Para entender melhor como os vazamentos de memória acontecem, é importante primeiro saber como os aplicativos iOS gerenciam sua memória. Vamos dar uma olhada em como evitar vazamentos de memória em fechamentos rápidos.

contagem automática de referência

no iOS, a memória compartilhada é gerenciada fazendo com que cada objeto acompanhe quantos outros objetos têm uma referência a ele. Assim que essa contagem de referência atingir 0, o que significa que não há mais referências ao objeto, ele pode ser limpo com segurança da memória. Como o nome implicaria, essa contagem de referência só é necessária para tipos de referência, enquanto os tipos de valor não requerem gerenciamento de memória. A contagem de referência foi feita manualmente no passado chamando retain em um objeto para aumentar sua contagem de referência e, em seguida, chamando release no objeto para diminuir sua contagem de referência. Este código era quase inteiramente placa de caldeira, chato para escrever e erro propenso. Assim como a maioria das tarefas domésticas, tornou-se automatizado por meio da contagem automática de referência (ARC), o que torna as chamadas necessárias de retenção e liberação em seu nome em tempo de compilação.

embora o ARC tenha diminuído principalmente a necessidade de se preocupar com o gerenciamento de memória, ele ainda pode criar vazamentos de memória rápidos sempre que houver referências circulares. Por exemplo, digamos que temos um Person classe que tem uma propriedade para um apartment e o Apartment classe tem um Person propriedade chamada 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

Quando criamos um novo Person e Apartment e atribuí-los a cada um dos outros’ propriedades, eles estão agora fazendo referência a cada um dos outros em uma forma circular. Referências circulares também podem acontecer com mais de dois objetos. Nessa situação, tanto o Person quanto o Apartment estão mantendo uma referência ao outro, portanto, neste caso, nenhum deles chegará a uma contagem de referência de 0 e se manterá na memória, embora nenhum outro objeto tenha referências a nenhum deles. Isso é conhecido como um ciclo de retenção e causa um vazamento de memória.

relacionado: três recursos que seriam realmente rápidos

a maneira como o ARC lida com os ciclos de retenção é ter diferentes tipos de referências: forte, fraco e sem dono. Referências fortes são do tipo que já falamos, e essas referências aumentam a contagem de referências de um objeto. Referências fracas, por outro lado, enquanto ainda lhe dão referência ao objeto, não aumentam sua contagem de referência. Então, se tomarmos a tenant propriedade Apartment e torná-lo uma referência fraca em vez disso, ele iria quebrar o nosso reter ciclo:

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

Agora o nosso Person objeto ainda está segurando o seu Apartment em memória, mas o inverso não é verdadeiro. Portanto, quando a última referência forte ao Person se foi, sua contagem de referência cairá para 0 e liberará seu Apartment, cuja contagem de referência também será reduzida para 0, e ambos podem ser limpos corretamente da memória.

em Swift, referências fracas devem ser opcionais var s porque, se você não está assumindo a responsabilidade de manter um objeto na memória, não pode garantir que o objeto não mude ou deixe a memória. É aqui que entra em jogo o terceiro tipo de referência. referências sem dono são como referências fracas, exceto que elas podem ser não opcionais lets, mas elas só devem ser usadas quando você tiver certeza de que o objeto nunca deve ser nil. Como força unwrapped optionals, você está dizendo ao compilador “não se preocupe, eu tenho isso. Confia em mim.”Mas, como referências fracas, a referência sem dono não está fazendo nada para manter o objeto na memória e, se ele deixar a memória e você tentar acessá-lo, seu aplicativo travará. Novamente, assim como a força desembrulhou optionals.Embora os ciclos de retenção sejam fáceis de ver com dois objetos apontando um para o outro, eles são mais difíceis de ver quando os fechamentos no Swift estão envolvidos, e é aqui que eu vi a maioria dos ciclos de retenção acontecer.

evitar reter ciclos em fechamentos

é importante lembrar que os fechamentos são tipos de referência no Swift e podem causar ciclos de retenção com a mesma facilidade, se não com mais facilidade, como as classes. Para que um encerramento seja executado posteriormente, ele precisa reter todas as variáveis necessárias para que seja executado. Da mesma forma que as classes, um encerramento captura referências como fortes por padrão. Um ciclo de retenção com um fechamento seria algo assim:

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

relacionado: Quando devo usar blocos e fechamentos ou delegados para retornos de chamada?

Nova chamada à ação

neste caso, a SomeObject classe tem uma forte referência para aClosure e aClosure capturou self (a SomeObject exemplo) fortemente também. É por isso que o Swift sempre faz você adicionar self. explicitamente enquanto estiver em um fechamento para ajudar a evitar que os programadores capturem acidentalmente self sem perceber e, portanto, provavelmente causando um ciclo de retenção.

para ter variáveis de captura de fechamento como fracas ou sem dono, você pode fornecer as instruções de fechamento sobre como capturar certas variáveis:

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

mais sobre a sintaxe de encerramento aqui.

a maioria dos exemplos de encerramento que eu vi em tutoriais ou exemplos parecem capturar self como sem dono e chamá-lo de um dia, já que capturar self como fraco o torna opcional, o que pode ser mais inconveniente para trabalhar. Mas, como aprendemos antes, isso é inerentemente inseguro, pois haverá uma falha se self não estiver mais na memória. Isso não é muito diferente do que apenas forçar o desembrulhamento de todas as suas variáveis opcionais, porque você não quer fazer o trabalho para desembrulhá-las com segurança. A menos que você possa ter certeza de que self estará por perto enquanto seu fechamento estiver, você deve tentar capturá-lo fraco. Se você precisar de um self Não opcional dentro do fechamento, considere usar um if let ou guard let para obter um self forte e não opcional dentro do fechamento. Como você fez essa nova e forte referência dentro do fechamento Swift, você não criará um ciclo de retenção, pois essa referência será liberada no final do fechamento:

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

ou ainda melhor:

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

capturando-se fortemente

embora seja uma boa prática capturar self fracamente em fechamentos, nem sempre é necessário. Fechamentos que não são retidos pelo self podem capturá-lo fortemente sem causar um ciclo de retenção. Alguns exemplos comuns são:

Trabalhando com DispatchQueues em GCD

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

Trabalhando com UIView.animate(withDuration:)

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

o primeiro é O de não manter o ciclo desde self não mantém o DispatchQueue.main singleton. No segundo exemplo, UIView.animate(withDuration:) é um método de classe, que self também não tem parte na retenção.

capturar self fortemente nessas situações não causará um ciclo de retenção, mas também pode não ser o que você deseja. Por exemplo, voltando ao GCD:

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

este fechamento não será executado por mais 60 segundos e manterá self até que isso aconteça. Este pode ser o comportamento que você deseja, mas se você quiser self para ser capaz de deixar a memória durante este tempo, seria melhor capturá – lo fracamente e apenas executar se self ainda estiver por perto:

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

Outro lugar interessante onde self não precisa ser capturado fortemente em lazy variáveis, que não são fechamentos, uma vez que será executado uma vez (ou nunca) e, em seguida, liberado depois:

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

no Entanto, se a lazy variável é um fechamento, ele precisa capturar auto fracamente. Um bom exemplo disso vem do Guia Swift Programming Language:

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

este também é um bom exemplo de uma instância que é razoável usar uma referência sem dono, já que o fechamento asHTML deve existir enquanto o HTMLElement existir, mas não mais.

TL;DR

Ao trabalhar com tampas de Swift, ser consciente de como você está capturando variáveis, particularmente selfs. Se self é manter o encerramento de qualquer forma, certifique-se de capturar fracamente. Apenas capture variáveis como sem dono quando puder ter certeza de que elas estarão na memória sempre que o fechamento for executado, não apenas porque você não deseja trabalhar com um selfopcional. Isso ajudará você a evitar vazamentos de memória em fechamentos rápidos, levando a um melhor desempenho do aplicativo.

Deixe uma resposta

O seu endereço de email não será publicado.