Sobes.tech
Middle

Como funciona a gestão automática de memória (ARC) em Swift?

sobes.tech IA

Resposta da IA

O ARC (Contador de Referências Automático) em Swift rastreia e gere o uso de memória das suas aplicações. Liberta automaticamente a memória utilizada pelas instâncias de classes quando estas deixam de ser necessárias, prevenindo fugas de memória.

Ao criar uma nova instância de uma classe, o ARC aloca um bloco de memória para armazenar informações sobre essa instância. Quando ela deixa de ser necessária, o ARC liberta essa memória para que possa ser reutilizada.

O ARC funciona contando o número de referências fortes (strong references) a cada instância de classe. Uma referência forte mantém a instância na memória. Quando o número de referências fortes atinge zero, o ARC liberta a memória associada.

Existem três tipos de referências em Swift que afetam o ARC:

  1. Referências Fortes (Strong References): Aumentam o contador de referências da instância.
  2. Referências Fracas (Weak References): Não aumentam o contador de referências. Usadas para evitar ciclos de referências fortes, quando duas instâncias de classes mantêm referências fortes entre si. A referência fraca torna-se automaticamente nil quando o objeto ao qual se refere é desinicializado.
  3. Referências Não Proprietárias (Unowned References): Também não aumentam o contador de referências. Usadas quando tem a certeza de que a instância referenciada sempre existirá enquanto a instância atual existir. Não podem ser opcionais (nil). Tentar aceder a um objeto desinicializado através de uma referência não proprietária causará um erro em tempo de execução.

Exemplo de ciclo de referências fortes:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) está a ser inicializado")
    }

    deinit {
        print("\(name) está a ser desinicializado")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Apartamento \(unit) está a ser inicializado")
    }

    deinit {
        print("Apartamento \(unit) está a ser desinicializado")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person não é desinicializado
unit4A = nil // Apartment não é desinicializado

Para resolver o ciclo, use referências fracas ou não proprietárias. Neste caso, tenant em Apartment deve ser uma referência fraca:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) está a ser inicializado")
    }

    deinit {
        print("\(name) está a ser desinicializado")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Uso de referência fraca

    init(unit: String) {
        self.unit = unit
        print("Apartamento \(unit) está a ser inicializado")
    }

    deinit {
        print("Apartamento \(unit) está a ser desinicializado")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person é desinicializado
unit4A = nil // Apartment é desinicializado

A escolha entre referência fraca e não proprietária depende da relação entre os objetos:

  • Fraca (Weak): Se um dos objetos pode tornar-se nil antes do outro ser desinicializado (valor opcional).
  • Não proprietária (Unowned): Se um dos objetos sempre existir enquanto o outro existir (valor não opcional).

O ARC também gere a memória usada por closures que capturam instâncias de classes. Ciclos de referências fortes podem ocorrer quando uma closure captura uma instância de classe e essa instância tem uma referência forte à closure. Para evitar isso, usam-se listas de captura (capture lists) nas closures.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        // Sem lista de captura, ciclo forte possível
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) está a ser desinicializado")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "olá, mundo")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement não é desinicializado

Solução usando lista de captura [weak self]:

class AnHTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Uso de weak self
        guard let self = self else { return "" } // Verificação de existência de self
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) está a ser desinicializado")
    }
}

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "olá, mundo")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement é desinicializado

O uso de [unowned self] é possível se estiver certo de que a instância da classe (self) não se tornará nil antes da execução da closure.

O ARC simplifica bastante a gestão de memória em Swift em comparação com a gestão manual, mas requer compreensão dos tipos de referências para evitar fugas de memória.