Sabe quando você está utilizando um aplicativo no seu iPhone e ele começa a ficar lento, travando e de repente o aplicativo fecha? Então, provavelmente ocorreu um problema conhecido como vazamento de memória. Para saber um pouco mais sobre esse tipo problema no iOS, confira o artigo.
Ciclo de vida de uma referência
Para entender o uso da memória no iOS, é fundamental entender como funciona o ciclo de vida de uma referência a uma classe. Esse ciclo compreende basicamente 5 etapas. São elas:
1. Alocação: nessa etapa é fornecido um espaço da memória do dispositivo, para que a referência possa ocupar;
2. Inicialização: em seguida, com o seu espaço garantido, o objeto pode ser inicializado pelo aplicativo;
3. Uso: agora, o objeto pode ser utilizado todas as vezes que for necessário;
4. Desinicialização: ao terminar de usar a referência, o objeto é desinicializado, mas continua ocupando o espaço na memória;
5. Desalocação: nessa última etapa, o espaço da memória é liberado, e pode ser usado para outras necessidades.
Vou utiizar uma analogia como exemplo. Imagine que você chegou em um restaurante e pediu uma mesa para o garçom. Nesse momento, estará sendo reservado um lugar para você se sentar, mesmo que você ainda não tenha sentado (Alocação). Em seguida, você se desloca até a mesa disponibilizada para você e se senta nela, ocupando o seu espaço (Inicialização). Durante um tempo, você estará usando essa mesa e pedindo tudo que tem direito (Uso). Após isso, você pagará a conta e sairá da mesa, mas ela ainda não vai estar disponível para uso, uma vez que ainda estará suja (Desinicialização). Então, o garçom volta à mesa para limpá-la e assim a deixa pronta para uso do próximo cliente (Desalocação).
Automatic Reference Counting (ARC)
O ARC é um contador automático de referências presente nas linguagens Swift e Objective-C. Com ele, toda vez que uma referência a uma classe é feita, um espaço de memória é destinado para armazenar informações sobre ela, e a cada referência a ela, uma unidade é incrementada ao seu contador automático. A cada referência tirada desse objeto, seu contador diminui, e quando não existirem mais referências a essa classe, seu contador retorna a zero. Assim o objeto é desalocado, parando de ocupar espaço na memória.
No exemplo a seguir, a classe Compasser possui um atributo name, um método de inicialização e um de desinicialização.
Ao criar as variáveis compasser 1, 2 e 3 do tipo Compasser como opcionais, inicialmente seus valores são dados como nulos e nenhum valor é incrementado ao ARC.
Em seguida, a classe Compasser é inicializada juntamente com a contagem do ARC.
E a seguinte mensagem aparece no console.
Ao atribuir valores para as variáveis compasser 2 e 3 iguais ao da “compasser1”, a contagem do ARC aumenta para 3.
Para diminuir a contagem do ARC as variáveis são dadas como nulas. Porém, enquanto existir referências para a classe e o ARC não for nulo, a variável não é desalocada da memória.
Assim que a última referência é dada como nula, o contador alcança o zero. A classe pode ser desinicializada da memória.
Quando isso ocorre, o desinicializador é chamado e a seguinte mensagem é impressa no console. Assim, a variável é desalocada da memória.
Apesar de parecer que com o ARC a memória não fica sendo ocupada com objetos que não são mais utilizados, problemas como a referência cíclica forte fazem com que o objeto fique inacessível, ocorrendo o vazamento de memória.
Referência do tipo Strong
Antes de aprender sobre a referência cíclica, é fundamental entender as referências do tipo forte. Elas ocorrem quando não se especifica nada antes da declaração da variável. Ao utilizar referências fortes, a classe só é desalocada quando não existirem mais referências ao objeto, como no exemplo a seguir.
Duas classes são declaradas, a Pet e a Human, sendo que esta possui como atributo forte Pet. Assim, sempre que a classe Human for inicializada, a classe Pet também será.
Inicialmente, declaramos a variável pedro inicializando a classe Human, assim, a Pet é inicializada como mostra o console.
Nesse caso, o ARC de Human e Pet sobem para 1. A seguir, uma ilustração para que fique mais fácil de visualizar.
Em seguida, declara-se pedro como nulo, fazendo com que sua contagem seja 0. Uma vez que não há mais referências à classe Human, ela é desalocada, e consequentemente Pet também.
A partir desse exemplo, foi possível compreender como funciona a referência do tipo forte. Agora ficará mais fácil para entender como acontece a referência cíclica.
Referência Cíclica Forte
A referência cíclica ocorre quando dois objetos são inicializados separadamente, e em um dado momento esses objetos apontam um para o outro com referências do tipo forte. Após esses objetos não terem mais referências individuais apontando para eles, continuam tendo essas referências entre si. Com isso, os objetos ficam inacessíveis e não podem ser desalocados. Parece meio confuso de entender, não é mesmo? Para entender melhor, veja o exemplo a seguir.
Nesse exemplo são criadas duas classes, Student, que contém name e favoriteBook como atributos, e a classe Book, que contém seu title e owner como atributos. Ambas possuem os métodos de inicialização e de desinicialização.
Em seguida, as variáveis maria e theLittlePrince são criadas, e, por serem opcionais e não terem sido inicializadas, a contagem do ARC ainda não foi incrementada.
A partir do momento que é atribuído um valor às variáveis, as classes Student e Book são inicializadas, como mostra o console.
A partir da ilustração, é possível ver as referências para cada classe e cada seta incrementa o ARC.
Em seguida, é atribuído um favoriteBook para maria e uma owner ao livro theLittlePrince. Assim, referências do tipo forte são criadas para cada classe e, logo, o ARC é incrementado.
Nota-se que maria aponta para theLittlePrince e theLittlePrince também aponta para maria.
Ao atribuir o valor nulo para as variáveis maria e theLittlePrince, o ARC decresce para um, mas não chega a 0, pois continua tendo a referência de theLittlePrince para maria, e maria para theLittlePrince, como mostra a figura.
Esse tipo de referência faz com que o ARC nunca chegue a zero, tornando as referências órfãs e inacessíveis, mesmo que elas continuem ocupando espaço na memória.
Referências do tipo Weak
As referências fracas permitem que haja relação entre duas classes, mas não aumenta a contagem do ARC, assim o objeto pode ser desalocado, mesmo que existam referências fracas apontando para ele. A forma de indicar que uma referência é fraca, é colocar a palavra “weak” antes da declaração da variável.
Uma característica marcante desse tipo de referência, é que ela não necessariamente deverá existir, sendo declarada como opcional. Por esse motivo, ela deverá sempre ser uma variável (var) e não uma constante (let).
O exemplo da referência fraca é o mesmo da referência cíclica, alterando apenas o tipo de referência da variável owner para weak.
As variáveis maria e theLittlePrince são criadas e em seguida inicializadas com seu nome e título, respectivamente. Ao declarar o favoriteBook de maria e o owner de theLittlePrince, as variáveis maria e theLittlePrince ficam ligadas entre si.
Na ilustração é possível ver como fica a relação entre Student e Book. Sendo a referência de Student para Book forte, o sentido contrário é fraco. Assim, o ARC de Student é igual a 1 e o ARC de Book é igual a 2.
Agora, ao declarar maria como nula, ela pode ser desinicializada, e a contagem do ARC para Book diminui para 1.
Em seguida, declara-se theLittlePrince como nulo seu ARC chega a 0, imprimindo a seguinte mensagem no console.
Assim, é possível perceber que o problema de referência cíclica foi resolvido, apenas colocando uma palavra antes da declaração da variável.
Referências do tipo Unowned
Uma referência unowned, traduzindo para o português, sem dono, é muito parecida com a referência fraca, pois também não incrementa a contagem do ARC. Porém, ao contrário da referência fraca, uma referência sem dono sempre vai receber um valor, então não pode ser opcional.
Por isso, o exemplo de uma referência sem dono, é o caso de uma classe Person e Email, pois uma pessoa pode existir sem um email (por isso o optional), porém um email não pode existir sem uma pessoa associada a ele.
Note que por cada classe referenciar uma à outra, as chances de uma referência cíclica ocorrer são altas. Para evitar isso, usa-se a palavra “unowned” antes do atributo person, já que ele sempre vai existir.
Declaramos a variável ana como opcional para que no final ela possa ser dada como nula e diminua a contagem do ARC.
Em seguida, inicializamos a classe Person e atribuímos um email para ana, inicializando Email consequentemente.
Assim que Person e Email são inicializadas, as seguintes mensagens aparecem no console.
A ilustração a seguir facilita a compreensão de como as referências estão funcionando.
Em seguida, declaramos ana como nula para que não existam mais referências fortes apontando para Person, assim a classe pode ser desinicializada. Quando isso acontece, a classe Email também perde a referência que aponta para ela e também é desinicializada.
A seguinte mensagem aparece no console quando ana é declarada como nula.
A ilustração a seguir representa a classe Person sem referências fortes apontando para ela, logo antes de ser desalocada.
Uma vez que a classe Person é desalocada, a classe Email também fica sem referências fortes apontando para ela, sendo desalocada da memória. Mais uma vez o problema de referência cíclica foi resolvido.
Espero que esse artigo tenha te ajudado a entender melhor como resolver problemas de memória no iOS. Para mais dicas, continue visitando nossa página.