La gestión de memoria es un aspecto crucial en cualquier lenguaje de programación, y Swift no es la excepción. En este módulo, aprenderás cómo Swift maneja la memoria de manera eficiente y cómo puedes optimizar el uso de la memoria en tus aplicaciones.
Conceptos Clave
- ARC (Automatic Reference Counting): Swift utiliza ARC para gestionar la memoria de las instancias de clases.
- Ciclos de Retención: Situaciones en las que dos o más instancias mantienen referencias fuertes entre sí, impidiendo que se libere la memoria.
- Referencias Fuertes y Débiles: Diferencias y usos de referencias fuertes (
strong
) y débiles (weak
). - Referencias No Propietarias: Uso de referencias no propietarias (
unowned
) para evitar ciclos de retención. - Desinicializadores: Métodos
deinit
para realizar tareas de limpieza antes de que una instancia sea desalojada de la memoria.
ARC (Automatic Reference Counting)
Swift utiliza ARC para seguir y gestionar la memoria de las instancias de clases. Cada vez que creas una nueva instancia de una clase, ARC asigna un bloque de memoria para almacenar información sobre esa instancia. ARC también realiza un seguimiento del número de referencias fuertes a cada instancia.
Ejemplo de ARC
class Person { let name: String init(name: String) { self.name = name print("\(name) is being initialized") } deinit { print("\(name) is being deinitialized") } } var reference1: Person? var reference2: Person? var reference3: Person? reference1 = Person(name: "John Appleseed") reference2 = reference1 reference3 = reference1 reference1 = nil reference2 = nil reference3 = nil
Explicación:
- Se crea una instancia de
Person
y se asigna areference1
. reference2
yreference3
también apuntan a la misma instancia.- Cuando todas las referencias se establecen en
nil
, ARC desalojará la memoria de la instancia dePerson
.
Ciclos de Retención
Un ciclo de retención ocurre cuando dos instancias mantienen referencias fuertes entre sí, lo que impide que ARC libere la memoria de esas instancias.
Ejemplo de Ciclo de Retención
class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String var tenant: Person? init(unit: String) { self.unit = unit } deinit { print("Apartment \(unit) is being deinitialized") } } 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
Explicación:
Person
yApartment
mantienen referencias fuertes entre sí.- Aunque
john
yunit4A
se establecen ennil
, las instancias no se desalojan debido al ciclo de retención.
Referencias Fuertes y Débiles
Para evitar ciclos de retención, puedes usar referencias débiles (weak
) o no propietarias (unowned
).
Referencias Débiles
Las referencias débiles no mantienen una referencia fuerte a la instancia, permitiendo que ARC la desaloje.
class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String weak var tenant: Person? init(unit: String) { self.unit = unit } deinit { print("Apartment \(unit) is being deinitialized") } } 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
Explicación:
tenant
enApartment
es una referencia débil.- Cuando
john
yunit4A
se establecen ennil
, ambas instancias se desalojan correctamente.
Referencias No Propietarias
Las referencias no propietarias (unowned
) se utilizan cuando se sabe que la referencia siempre tendrá un valor durante su ciclo de vida.
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class CreditCard { let number: UInt64 unowned let customer: Customer init(number: UInt64, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #\(number) is being deinitialized") } } var john: Customer? john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) john = nil
Explicación:
customer
enCreditCard
es una referencia no propietaria.- Cuando
john
se establece ennil
, ambas instancias se desalojan correctamente.
Desinicializadores
Los desinicializadores (deinit
) se utilizan para realizar tareas de limpieza antes de que una instancia sea desalojada de la memoria.
Ejemplo de Desinicializador
class BankAccount { let accountNumber: String init(accountNumber: String) { self.accountNumber = accountNumber print("Account \(accountNumber) is being initialized") } deinit { print("Account \(accountNumber) is being deinitialized") } } var account: BankAccount? = BankAccount(accountNumber: "1234567890") account = nil
Explicación:
- El desinicializador se llama automáticamente cuando la instancia de
BankAccount
se establece ennil
.
Ejercicios Prácticos
Ejercicio 1: Evitar Ciclos de Retención
Crea dos clases, Teacher
y Student
, donde un Teacher
puede tener múltiples Student
y un Student
puede tener un Teacher
. Asegúrate de evitar ciclos de retención.
Solución:
class Teacher { let name: String var students: [Student] = [] init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Student { let name: String weak var teacher: Teacher? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } var teacher: Teacher? = Teacher(name: "Mr. Smith") var student1: Student? = Student(name: "Alice") var student2: Student? = Student(name: "Bob") teacher!.students.append(student1!) teacher!.students.append(student2!) student1!.teacher = teacher student2!.teacher = teacher teacher = nil student1 = nil student2 = nil
Ejercicio 2: Uso de Referencias No Propietarias
Crea una clase Company
y una clase Employee
donde un Employee
siempre tiene una Company
, y una Company
puede tener múltiples Employee
. Usa referencias no propietarias para evitar ciclos de retención.
Solución:
class Company { let name: String var employees: [Employee] = [] init(name: String) { self.name = name } deinit { print("Company \(name) is being deinitialized") } } class Employee { let name: String unowned let company: Company init(name: String, company: Company) { self.name = name self.company = company } deinit { print("Employee \(name) is being deinitialized") } } var company: Company? = Company(name: "Tech Corp") var employee1: Employee? = Employee(name: "Alice", company: company!) var employee2: Employee? = Employee(name: "Bob", company: company!) company!.employees.append(employee1!) company!.employees.append(employee2!) company = nil employee1 = nil employee2 = nil
Conclusión
En esta sección, has aprendido cómo Swift gestiona la memoria utilizando ARC, cómo evitar ciclos de retención utilizando referencias débiles y no propietarias, y cómo utilizar desinicializadores para realizar tareas de limpieza. Estos conceptos son fundamentales para escribir código eficiente y libre de errores en Swift. En el próximo módulo, exploraremos la concurrencia en Swift, un tema avanzado que te permitirá escribir aplicaciones más rápidas y eficientes.
Curso de Programación en Swift
Módulo 1: Introducción a Swift
- Introducción a Swift
- Configuración del Entorno de Desarrollo
- Tu Primer Programa en Swift
- Sintaxis y Estructura Básica
- Variables y Constantes
- Tipos de Datos
Módulo 2: Control de Flujo
Módulo 3: Funciones y Closures
- Definición y Llamada de Funciones
- Parámetros de Función y Valores de Retorno
- Closures
- Funciones de Orden Superior
Módulo 4: Programación Orientada a Objetos
Módulo 5: Swift Avanzado
Módulo 6: Swift y Desarrollo de iOS
- Introducción al Desarrollo de iOS
- Fundamentos de UIKit
- Storyboards y Interface Builder
- Redes en Swift
- Core Data
- Fundamentos de SwiftUI