Sobes.tech
Middle

Fale-nos sobre a gestão de memória no iOS.

sobes.tech IA

Resposta da IA

O iOS utiliza contagem automática de referências (ARC) para gerir a memória. O ARC liberta automaticamente os objetos assim que deixam de ser referenciados.

Conceitos principais:

  • Referência forte: Aumenta o contador de referências do objeto. O objeto não será libertado enquanto existirem referências fortes.
  • Referência fraca: Não aumenta o contador de referências. Usada para evitar ciclos de referências fortes. Refere-se a um objeto que pode ser libertado. Se o objeto for libertado, a referência fraca torna-se nil.
  • Referência não possuída (unowned): Semelhante à fraca, não aumenta o contador de referências. Usada quando o objeto referenciado tem um ciclo de vida igual ou mais longo. A referência não possuída não se torna nil ao libertar o objeto. Tentar aceder a um objeto libertado através de uma referência não possuída gera um erro em tempo de execução.

Ciclos de referências fortes (Retain Cycles): Ocorrem quando dois ou mais objetos referenciam-se mutuamente com referências fortes, impedindo que sejam libertados. São resolvidos usando referências fracas ou não possuídas.

Exemplo de ciclo de referências fortes e a sua solução:

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? // Referência forte que causa ciclo de retenção

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

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

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

john!.apartment = unit4A
unit4A!.tenant = john // Aqui ocorre um ciclo de retenção

john = nil
unit4A = nil
// Os métodos de desinicialização não são chamados devido ao ciclo de retenção

Solução usando weak:

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? // Usa referência weak para evitar ciclo de retenção

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

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

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

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

john = nil
unit4A = nil
// Ambos objetos são desinicializados corretamente

Não possuído (unowned) vs Fraco (weak):

  • Usa weak quando a referência pode tornar-se nil (o objeto pode ser libertado primeiro).
  • Usa unowned quando a referência sempre terá um valor (o objeto referenciado tem um ciclo de vida igual ou mais longo).

Closures e ciclos de referências:

As closures podem capturar referências a objetos, criando ciclos de referências fortes. Isto resolve-se com uma lista de captura (capture list).

Exemplo:

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

    lazy var asHTML: () -> String = {
        [weak self] in // Captura fraca de self
        guard let self = self else {
            return ""
        }
        var html = "<\(self.name)"
        if let text = self.text {
            html += ">\(text)</\(self.name)>"
        } else {
            html += " />"
        }
        return html
    }

    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
// O objeto é desinicializado

Depuração de problemas de memória:

  • Memory Graph Debugger: No Xcode, permite visualizar as ligações entre objetos e identificar ciclos de referências fortes.
  • Instruments (Allocations, Leaks): Ferramentas para monitorizar a alocação e libertação de memória, e detectar fugas.