Décio Montanhani

Gerenciamento de memória no iOS — weak, strong e unowned

08/04/2019gerenciamento-de-memoriaiosswift

Quando comecei a trabalhar com Swift me deparei com alguns termos diferentes dos habituais. Um destes termos (weak) apareceu quando criei um IBOutlet pelo StoryBoard. Conversando com um colega do meu time, descobri que se relacionava ao ARC. Mas, afinal, o que é ARC? E o que as palavras weak, strong e unowned representam?

ARC (Automatic Reference Counting)

O ARC, como o próprio nome diz, é um recurso de gerenciamento de memória, que conta o número de referências que um objeto tem para que, caso esse não tenha nenhuma referência forte, ele desaloque da memória.

Mas onde entra o weak, strong e unowned nessa história?

O ARC conta a referência do objeto das variáveis que são referenciadas como strong, e quando é utilizado como weak ou unowned não é contabilizado. Ou seja, se o objeto não tiver nenhuma referência forte, o ARC poderá liberar memória.

O problema é quando acontece uma referência cíclica, que é quando uma instância do objeto A não consegue ser liberada pelo ARC, porque está amarrada a outra (objeto B), que não consegue ser liberada, e vice-versa, ou seja, a instância do objeto B, também, está presa à instância do objeto A.

referência cíclica.

Basicamente, referências fortes são usadas para descrever o relacionamento entre objetos. Quando um objeto tem uma forte referência a outro objeto, isso cria um ciclo de retenção impedindo que o objeto referenciado seja desalocado e, consequentemente, aumenta a contagem de retenções para 1. Em outras palavras, esses objetos agora estão se mantendo vivos.

Chega de enrolação, vamos ao exemplo!

Show me the code!

Primeiro vamos criar duas classes para entendermos como as coisas funcionam, segue o código:

class Pessoa {
    var nome: String
    var veiculo: Carro?

    init(nome: String, veiculo: Carro?) {
        self.nome = nome
        self.veiculo = veiculo
    }

    deinit {
        print("\(nome) está sendo liberado")
    }
}

class Carro {
    var nome: String
    var proprietario: Pessoa?

    init(nome: String, proprietario: Pessoa?) {
        self.nome = nome
        self.proprietario = proprietario
    }

    deinit {
        print("\(nome) está sendo liberado")
    }
}

Aqui nesse exemplo (um pouco tradicional) de carro com pessoa, podemos ver que ambos os objetos têm uma dependência um do outro. Vamos inicializar esses objetos e preencher as propriedades para vermos onde acontece o problema:

var decio: Pessoa?
var fusca: Carro?

decio = Pessoa("Décio", nil)
fusca = Carro("Fusca", nil)

decio?.veiculo = fusca
fusca?.proprietario = decio

decio = nil

Se rodarmos esse código, podemos ver que não será printado no console que o objeto decio foi desalocado. Isso acontece porque, ao criar o objeto decio, ele incrementará um no ARC, e após atribuí-lo como proprietário do objeto fusca, ele também incrementará no ARC. O ARC ficará na contagem 2.

Então, ao associar o decio a nil, o objeto ainda não será liberado, pois o ARC estará com uma contagem que é a referência forte no objeto fusca.

Ok, e como resolvemos isso?

Para resolvermos essa referência cíclica, temos que criar uma referência fraca do pai no objeto filho, para que, quando nós atribuirmos nil a ele, ele não tenha nenhuma referência no ARC e possa ser desalocado de memória.

class Carro {
    var nome: String
    weak var proprietario: Pessoa?

    init(nome: String, proprietario: Pessoa?) {
        self.nome = nome
        self.proprietario = proprietario
    }

    deinit {
        print("\(nome) está sendo liberado")
    }
}

Beleza, mas qual a diferença entre o weak e o unowned?

A primeira diferença que você precisa saber é que o unowned é sempre esperado um valor. Já não é a mesma coisa para o weak, que são definidas como nulas se a instância que elas referenciam for desalocada. Quando isso acontece, a referência é definida como nula.

Como uma referência fraca (weak) pode ser definida como nula, ela é sempre declarada como opcional. Essa é a segunda diferença entre referências weak e unowned. O valor de uma referência fraca precisa ser desembrulhado antes de poder ser acessado, enquanto você pode acessar diretamente o valor de uma referência unowned.

Mas preste atenção, porque, se a instância referenciada de um unowned for desalocada, ela não será definida como nula. Como resultado, um fatal error é lançado e seu app pode tomar um crash.

Arquivos de referência:

Apple Documentation — Automatic Reference Counting

“Weak, Strong, Unowned, Oh My!” — A Guide To References In Swift

© Décio Montanhani 2023