La gestió de memòria és un aspecte crucial en qualsevol llenguatge de programació, i Swift no és una excepció. En aquest tema, explorarem com Swift gestiona la memòria, incloent conceptes com el comptatge automàtic de referències (ARC), cicles de referència i com evitar-los.
Continguts
Comptatge Automàtic de Referències (ARC)
Swift utilitza el Comptatge Automàtic de Referències (ARC) per gestionar la memòria dels objectes. ARC fa un seguiment del nombre de referències fortes a cada instància d'objecte i allibera la memòria quan ja no hi ha referències fortes a l'objecte.
Com Funciona ARC
- Referència Forta: Cada vegada que una instància d'una classe és assignada a una variable, constant o propietat, es crea una referència forta.
- Increment de Comptador: ARC incrementa el comptador de referències cada vegada que es crea una nova referència forta.
- Decrement de Comptador: ARC decrementa el comptador de referències cada vegada que una referència forta es destrueix.
- Alliberament de Memòria: Quan el comptador de referències arriba a zero, ARC allibera la memòria ocupada per l'objecte.
Exemple
class Persona { var nom: String init(nom: String) { self.nom = nom } } var persona1: Persona? = Persona(nom: "Joan") var persona2: Persona? = persona1 persona1 = nil persona2 = nil // En aquest punt, l'objecte Persona és alliberat de la memòria perquè no hi ha referències fortes.
Cicles de Referència
Un cicle de referència es produeix quan dues o més instàncies es referencien mútuament, impedint que ARC alliberi la memòria d'aquestes instàncies.
Exemple de Cicle de Referència
class Persona { var nom: String var mascota: Mascota? init(nom: String) { self.nom = nom } } class Mascota { var nom: String var propietari: Persona? init(nom: String) { self.nom = nom } } var joan: Persona? = Persona(nom: "Joan") var fido: Mascota? = Mascota(nom: "Fido") joan?.mascota = fido fido?.propietari = joan joan = nil fido = nil // En aquest punt, l'objecte Persona i l'objecte Mascota no són alliberats de la memòria perquè hi ha un cicle de referència.
Referències Febles i Sense Propietari
Per evitar cicles de referència, podem utilitzar referències febles (weak
) i referències sense propietari (unowned
).
Referències Febles
Les referències febles no incrementen el comptador de referències. S'utilitzen quan una referència pot ser nil
en algun moment.
class Persona { var nom: String var mascota: Mascota? init(nom: String) { self.nom = nom } } class Mascota { var nom: String weak var propietari: Persona? init(nom: String) { self.nom = nom } } var joan: Persona? = Persona(nom: "Joan") var fido: Mascota? = Mascota(nom: "Fido") joan?.mascota = fido fido?.propietari = joan joan = nil fido = nil // En aquest punt, l'objecte Persona i l'objecte Mascota són alliberats de la memòria perquè no hi ha cicle de referència.
Referències Sense Propietari
Les referències sense propietari (unowned
) tampoc incrementen el comptador de referències. S'utilitzen quan una referència mai serà nil
després de ser inicialitzada.
class Persona { var nom: String var targeta: Targeta? init(nom: String) { self.nom = nom } } class Targeta { var numero: Int unowned var propietari: Persona init(numero: Int, propietari: Persona) { self.numero = numero self.propietari = propietari } } var joan: Persona? = Persona(nom: "Joan") var targeta: Targeta? = Targeta(numero: 1234, propietari: joan!) joan?.targeta = targeta joan = nil // En aquest punt, l'objecte Persona i l'objecte Targeta són alliberats de la memòria perquè no hi ha cicle de referència.
Exemples Pràctics
Exemple 1: Evitant Cicles de Referència amb weak
class Node { var valor: Int weak var pare: Node? var fills: [Node] = [] init(valor: Int) { self.valor = valor } func afegirFill(_ fill: Node) { fills.append(fill) fill.pare = self } } let arrel = Node(valor: 1) let fill1 = Node(valor: 2) let fill2 = Node(valor: 3) arrel.afegirFill(fill1) arrel.afegirFill(fill2) // No hi ha cicle de referència perquè la referència al pare és feble.
Exemple 2: Evitant Cicles de Referència amb unowned
class Persona { var nom: String var targeta: Targeta? init(nom: String) { self.nom = nom } } class Targeta { var numero: Int unowned var propietari: Persona init(numero: Int, propietari: Persona) { self.numero = numero self.propietari = propietari } } var joan: Persona? = Persona(nom: "Joan") var targeta: Targeta? = Targeta(numero: 1234, propietari: joan!) joan?.targeta = targeta joan = nil // No hi ha cicle de referència perquè la referència al propietari és sense propietari.
Exercicis
Exercici 1: Identificar Cicles de Referència
Descripció: Analitza el següent codi i identifica si hi ha un cicle de referència. Si n'hi ha, corregeix-lo.
class Autor { var nom: String var llibre: Llibre? init(nom: String) { self.nom = nom } } class Llibre { var titol: String var autor: Autor? init(titol: String) { self.titol = titol } } var autor: Autor? = Autor(nom: "Gabriel Garcia Marquez") var llibre: Llibre? = Llibre(titol: "Cien Años de Soledad") autor?.llibre = llibre llibre?.autor = autor autor = nil llibre = nil
Solució:
class Autor { var nom: String var llibre: Llibre? init(nom: String) { self.nom = nom } } class Llibre { var titol: String weak var autor: Autor? // Canviem a weak per evitar el cicle de referència init(titol: String) { self.titol = titol } } var autor: Autor? = Autor(nom: "Gabriel Garcia Marquez") var llibre: Llibre? = Llibre(titol: "Cien Años de Soledad") autor?.llibre = llibre llibre?.autor = autor autor = nil llibre = nil
Exercici 2: Crear una Jerarquia d'Objectes Sense Cicles de Referència
Descripció: Crea una jerarquia d'objectes que representi una empresa amb empleats i projectes. Assegura't que no hi hagi cicles de referència.
Solució:
class Empleat { var nom: String var projectes: [Projecte] = [] init(nom: String) { self.nom = nom } func afegirProjecte(_ projecte: Projecte) { projectes.append(projecte) projecte.empleat = self } } class Projecte { var nom: String weak var empleat: Empleat? init(nom: String) { self.nom = nom } } let empleat = Empleat(nom: "Anna") let projecte1 = Projecte(nom: "Projecte A") let projecte2 = Projecte(nom: "Projecte B") empleat.afegirProjecte(projecte1) empleat.afegirProjecte(projecte2) // No hi ha cicle de referència perquè la referència a l'empleat és feble.
Conclusió
En aquesta secció, hem après com Swift gestiona la memòria utilitzant el Comptatge Automàtic de Referències (ARC) i com evitar cicles de referència utilitzant referències febles (weak
) i sense propietari (unowned
). Aquests conceptes són fonamentals per escriure codi eficient i lliure de fuites de memòria. En el següent tema, explorarem la concurrència en Swift, un altre aspecte crucial per desenvolupar aplicacions eficients i responsives.
Curs de Programació en Swift
Mòdul 1: Introducció a Swift
- Introducció a Swift
- Configuració de l'Entorn de Desenvolupament
- El Teu Primer Programa en Swift
- Sintaxi i Estructura Bàsica
- Variables i Constants
- Tipus de Dades
Mòdul 2: Flux de Control
Mòdul 3: Funcions i Closures
- Definició i Crida de Funcions
- Paràmetres de Funció i Valors de Retorn
- Closures
- Funcions d'Ordre Superior
Mòdul 4: Programació Orientada a Objectes
Mòdul 5: Swift Avançat
Mòdul 6: Swift i Desenvolupament iOS
- Introducció al Desenvolupament iOS
- Conceptes Bàsics de UIKit
- Storyboards i Interface Builder
- Xarxes en Swift
- Core Data
- Conceptes Bàsics de SwiftUI