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
deinitpara 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 = nilExplicación:
- Se crea una instancia de
Persony se asigna areference1. reference2yreference3tambié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 = nilExplicación:
PersonyApartmentmantienen referencias fuertes entre sí.- Aunque
johnyunit4Ase 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 = nilExplicación:
tenantenApartmentes una referencia débil.- Cuando
johnyunit4Ase 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 = nilExplicación:
customerenCreditCardes una referencia no propietaria.- Cuando
johnse 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 = nilExplicación:
- El desinicializador se llama automáticamente cuando la instancia de
BankAccountse 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 = nilEjercicio 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 = nilConclusió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
