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:
- Referências Fortes (Strong References): Aumentam o contador de referências da instância.
- 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
nilquando o objeto ao qual se refere é desinicializado. - 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
nilantes 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.