Sobes.tech
Middle

Como funciona o mecanismo de gestão automática de memória (ARC) na linguagem Swift?

sobes.tech IA

Resposta da IA

ARC (Contagem Automática de Referências) rastreia e gere automaticamente as referências às instâncias de classes. Liberta a memória ocupada por uma instância quando já não há referências fortes a ela. O ARC funciona apenas com instâncias de classes, não com estruturas ou enumerações, pois são tipos de valor.

Três tipos de ligações entre instâncias:

  • Strong (Forte): Uma ligação que aumenta o contador de referências da instância. Por padrão, todas as ligações são fortes.
  • Weak (Fraca): Uma ligação que não aumenta o contador de referências. Usada para evitar ciclos de referências fortes. As ligações do tipo weak são sempre opcionais, pois a instância pode ser libertada a qualquer momento.
  • Unowned (Sem proprietário): Uma ligação que não aumenta o contador de referências e é usada quando se garante que a instância relacionada existirá por mais tempo que a atual. Não é obrigatório, mas tentar aceder a uma instância libertada provocará um erro em tempo de execução.

Os ciclos de referências fortes ocorrem quando duas ou mais instâncias de classes têm referências fortes entre si, impedindo a sua libertação pelo ARC. Para resolver estes ciclos, usam-se referências weak ou unowned.

Exemplo de ciclo de referências fortes:

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

    init(name: String) {
        self.name = name
    }

    deinit { // chamado na libertação
        print("\(name) está a ser desalojado")
    }
}

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

    init(unit: String) {
        self.unit = unit
    }

    deinit { // chamado na libertação
        print("Apartamento \(unit) está a ser desalojado")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil
unit4A = nil // Nada é impresso, pois há um ciclo de referências fortes

Resolução do ciclo com weak:

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

    init(name: String) {
        self.name = name
    }

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

class Apartment {
    let unit: String
    weak var tenant: Person? // Referência fraca a Person

    init(unit: String) {
        self.unit = unit
    }

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

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Liberta a Person
unit4A = nil // Liberta o Apartment

Resolução com unowned:

class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) {
        self.name = name
    }

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

class CreditCard {
    let number: UInt64
    unowned let customer: Customer // Referência sem proprietário a Customer

    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }

    deinit {
        print("Cartão #\(number) está a ser desalojado")
    }
}

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil // Liberta Customer e CreditCard

ARC em closures: As closures também podem criar ciclos de referências fortes, capturando instâncias de classes. Para evitar isso, usam-se listas de captura ([weak self] ou [unowned self]).

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

    lazy var asHTML: () -> String = { // lazy para poder usar self antes da inicialização
        [unowned self] in // Lista de captura com "unowned 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 desalojado")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // Liberta HTMLElement