Delegate: qué es, el patrón de delegación y cómo funciona en iOS

Autor: IT Sectr Publicado: 2026-03-17 Tiempo de lectura: 10 min

Delegate es un patrón de diseño en el que un objeto delega la ejecución de tareas a otro objeto a través de un protocolo con métodos predefinidos. En el desarrollo iOS, Delegate es uno de los patrones fundamentales de Cocoa Touch, utilizado para notificaciones asíncronas sin acoplamiento directo entre emisor y receptor. Según Apple Documentation (2025), la delegación se utiliza en Foundation y UIKit para manejar eventos de tablas, solicitudes de red y gestión de ubicación. El patrón garantiza un acoplamiento débil de componentes y la reutilización de código.

Puntos clave

  • Delegate es un patrón en el que un objeto encomienda el manejo de eventos a otro objeto a través de un protocolo.
  • Protocol en Swift define un conjunto de métodos que un delegate debe o puede implementar.
  • Weak reference es obligatoria para la propiedad delegate para evitar retain cycles.
  • @objc optional permite que los métodos del protocolo sean opcionales de implementar.
  • URLSessionDelegate es un delegate asíncrono para manejar eventos de solicitudes de red.

¿Qué es Delegate?

Delegate (delegado) es un objeto que implementa un protocolo específico y recibe notificaciones sobre eventos de otro objeto. El patrón Delegation es una alternativa a la herencia: en lugar de crear una subclase para sobrescribir métodos, un objeto delega el manejo de eventos a un objeto externo. En iOS, la delegación se implementa a través de protocolos Swift con métodos obligatorios y opcionales. La propiedad delegate siempre se declara como weak var para evitar referencias circulares entre objetos.

Definición del protocolo Delegate

Un protocolo delegate define el contrato de interacción entre objetos. Los métodos obligatorios deben ser implementados por el delegado, de lo contrario el código no compilará. Los métodos opcionales se marcan con el atributo @objc optional y permiten que el delegado responda solo a los eventos relevantes. Los nombres de los métodos siguen una convención: el primer parámetro es el objeto remitente, el segundo son los datos del evento. Por ejemplo, tableView(_:didSelectRowAt:) indica que el remitente es UITableView y los datos son el índice de la fila seleccionada.

swift
// Protocolo Delegate
protocol DownloadManagerDelegate: AnyObject {
    func downloadManager(_ manager: DownloadManager,
                          didFinishWith data: Data)
    func downloadManager(_ manager: DownloadManager,
                          didFailWith error: Error)

    @objc optional func downloadManager(_ manager: DownloadManager,
                                didUpdateProgress progress: Float)
}

// Clase que usa Delegate
class DownloadManager {
    weak var delegate: DownloadManagerDelegate?

    func startDownload(from url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let self else { return }
            if let error = error {
                self.delegate?.downloadManager(self, didFailWith: error)
            } else if let data = data {
                self.delegate?.downloadManager(self, didFinishWith: data)
            }
        }.resume()
    }
}

Referencia débil al delegate

La propiedad delegate debe declararse como weak var para prevenir retain cycles. Si la referencia fuera fuerte, el delegado y el objeto delegante se mantendrían mutuamente, y ARC no podría liberar su memoria. Los protocolos delegate heredan AnyObject (solo clases), lo que permite usar weak. Las estructuras y enumeraciones no pueden ser delegados debido a la semántica de valor. Una alternativa para value types son los callback closures.

swift
class ViewController: DownloadManagerDelegate {
    let manager = DownloadManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        manager.delegate = self // weak — sin retain cycle
        manager.startDownload(from: url)
    }

    func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
        processData(data)
    }

    func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
        showError(error)
    }
}

¿Cómo funciona el patrón Delegate en iOS?

El patrón Delegate funciona uno a uno: un objeto remitente puede tener solo un delegate en un momento dado. Cuando ocurre un evento, el remitente verifica si el delegate está establecido y llama al método correspondiente del protocolo. La ventaja sobre las llamadas directas es que el remitente no conoce el tipo del delegate, solo que cumple con el protocolo. Esto cumple con el Principio de Inversión de Dependencias (DIP) de SOLID.

Ciclo de vida del delegado

El delegado se asigna mediante asignación: someObject.delegate = self. Cuando el delegado se desasigna, la propiedad automáticamente se vuelve nil debido a la semántica weak. Antes de llamar a un método del delegado, se verifica mediante optional chaining: delegate?.method(). Si delegate es nil, la llamada se ignora sin crash. Para métodos opcionales del protocolo, se utiliza una verificación adicional: delegate?.responds(to: #selector(...)), aunque en Swift esta verificación suele ser implícita mediante la declaración de métodos opcionales.

Notificación asíncrona mediante Delegate

En un entorno multiproceso, delegate se utiliza para la devolución asíncrona de resultados. URLSession proporciona URLSessionDelegate con métodos que se llaman al recibir datos, por tiempo de espera o error de autenticación. Los métodos del delegate se ejecutan en la cola en segundo plano de URLSession, por lo que se requiere despachar a la cola principal para las actualizaciones de la UI. El delegate asíncrono no bloquea el hilo de llamada, permitiendo continuar con otras tareas.

swift
class NetworkService: NSObject, URLSessionDataDelegate {
    private lazy var session = URLSession(
        configuration: .default,
        delegate: self,
        delegateQueue: OperationQueue()
    )
    private var receivedData = Data()

    func urlSession(_ session: URLSession,
                    dataTask: URLSessionDataTask,
                    didReceive data: Data) {
        receivedData.append(data)
        let progress = Float(receivedData.count) / Float(expectedSize)
        DispatchQueue.main.async {
            self.progressHandler?(progress)
        }
    }

    func urlSession(_ session: URLSession,
                    task: URLSessionTask,
                    didCompleteWithError error: Error?) {
        if let error = error {
            delegate?.networkService(self, didFailWith: error)
        } else {
            delegate?.networkService(self, didReceive: receivedData)
        }
    }
}

Delegate vs Callback: comparación de enfoques

Delegate y Callback resuelven el mismo problema — notificación asíncrona — pero de diferentes maneras. Delegate utiliza un protocolo con métodos nombrados, callback utiliza un closure con captura de contexto. La elección depende del número de eventos, la complejidad de las firmas y las preferencias arquitectónicas. Apple recomienda delegate para APIs con múltiples eventos (UITableView — 20+ métodos) y callback para finalizaciones únicas.

Cuándo gana Delegate

Delegate es preferible cuando se manejan múltiples eventos diferentes de una sola fuente. Por ejemplo, CLLocationManager notifica a su delegado sobre cambios de ubicación, errores de permisos, entrada/salida de geocercas y cambios en el estado del servicio. Cada evento es un método de protocolo separado con un nombre claro y parámetros tipados. Delegate también es conveniente para la configuración del comportamiento (métodos should, will, did).

Cuándo gana Callback

Callback es más simple para solicitudes únicas con un solo resultado. Completion handler en URLSession.dataTask ocupa una línea en el punto de llamada frente a al menos tres métodos del protocolo. Callback también es más natural para cadenas funcionales (map, flatMap, async/await). Sin embargo, con anidamiento de más de 2-3 niveles, callback se convierte en Callback Hell, mientras que delegate siempre permanece plano.

Delegados integrados en iOS SDK

El SDK de iOS contiene docenas de protocolos delegate integrados para varios subsistemas. Cada uno está diseñado para un escenario de interacción específico. Según Apple Documentation (2025), los delegados más utilizados son UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate y UNUserNotificationCenterDelegate. Estos protocolos contienen de 3 a 30 métodos con distintos niveles de obligatoriedad.

UITableViewDelegate

UITableViewDelegate gestiona la apariencia y el comportamiento de las celdas de la tabla. Contiene métodos para manejar la selección de filas, configurar la altura de las celdas, vistas personalizadas de header/footer y acciones de deslizamiento. Todos los métodos del protocolo son opcionales, lo que permite implementar solo la funcionalidad necesaria. Sin delegado, la tabla funciona con la configuración predeterminada. Históricamente, delegate se combinaba con UITableViewDataSource.

URLSessionDelegate

URLSessionDelegate proporciona un control detallado sobre las solicitudes HTTP. Los métodos del delegado se llaman al recibir la respuesta del servidor, los datos o al completar la descarga. Los subprotocolos especializados URLSessionTaskDelegate y URLSessionDataDelegate extienden la funcionalidad base para tipos de tareas específicos. Se requiere delegate para admitir descargas en segundo plano, certificados SSL y manejo personalizado de redirecciones.

DelegateMétodosPropósito
UITableViewDelegate25Apariencia e interacción de la tabla
UITextFieldDelegate8Manejo de entrada de texto y teclado
CLLocationManagerDelegate12Actualizaciones de ubicación y geocercas
URLSessionDelegate6Gestión de sesión HTTP y certificados
UNUserNotificationCenterDelegate4Manejo de notificaciones push en primer plano

Gestión de memoria con Delegate

La gestión de memoria es un aspecto crítico del trabajo con delegate en iOS. ARC (Automatic Reference Counting) gestiona la memoria automáticamente, pero solo con el uso correcto de referencias weak/unowned. Violar las reglas provoca fugas de memoria o desasignación prematura. Un delegate declarado como strong crea un retain cycle si el propietario del delegado también mantiene una referencia al objeto delegante.

Retain cycle mediante Delegate

Un retain cycle ocurre cuando el objeto A (propietario) se establece como delegado del objeto B, y B mantiene una referencia fuerte al delegado. Ejemplo: ViewController crea URLSession, se establece como delegado de la sesión, pero URLSession por defecto mantiene una referencia fuerte al delegado si no se especifica delegateQueue. La solución es siempre verificar la documentación de la API sobre el tipo de referencia del delegado (weak o strong) y establecer explícitamente el delegado a nil en deinit.

swift
class SafeViewController: UIViewController {
    private var session: URLSession?
    private var service: NetworkService?

    override func viewDidLoad() {
        super.viewDidLoad()
        service = NetworkService()
        service?.delegate = self
    }

    deinit {
        // Poner delegate a nil en deinit — best practice
        service?.delegate = nil
        session?.invalidateAndCancel()
    }
}

// URLSession con weak delegate mediante NSObject
class WeakDelegateSession: NSObject {
    private weak var delegate: URLSessionDelegate?

    func createSession() -> URLSession {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        return URLSession(
            configuration: .default,
            delegate: self,
            delegateQueue: queue
        )
    }
}

Verificación segura del delegate antes de llamar

Antes de llamar a un método del delegado, debes verificar que el delegado exista (no sea nil) e implemente el método llamado. Para métodos obligatorios del protocolo, no se necesita verificación — el compilador garantiza la implementación. Para métodos opcionales, usa respond(to:) u optional chaining. Si el delegado se desasigna, la referencia weak automáticamente se vuelve nil, y la llamada al delegado se ignora. Este comportamiento es seguro y no requiere manejo adicional.

Errores típicos al implementar Delegate

Los desarrolladores suelen cometer errores al trabajar con el patrón Delegate, especialmente en las primeras etapas del aprendizaje de iOS. Los más comunes incluyen: retain cycle debido a delegate strong, olvidar llamar a delegate?.method(), firma incorrecta de métodos del protocolo, establecer el delegate después de iniciar una operación y colisiones de subprocesos múltiples. Veamos cada error y cómo prevenirlo.

Referencia strong en lugar de weak

El error más crítico es declarar la propiedad delegate como strong var en lugar de weak var. Esto crea un retain cycle donde ni el delegado ni el objeto delegante pueden liberarse. Consecuencias: fugas de memoria, ralentización de la aplicación y errores ocultos. Solución: usa siempre weak var para delegate, y haz que el protocolo herede de AnyObject para evitar el uso de value types como delegados.

Establecer el delegate después de iniciar una operación

Si el delegado se establece después de llamar a un método asíncrono, los primeros eventos pueden perderse. Ejemplo: llamar a startDownload() antes de asignar manager.delegate = self provoca la pérdida del callback de finalización si la descarga se ejecuta sincrónicamente o muy rápido. Solución: establece el delegate antes de llamar al método asíncrono y documenta el orden de inicialización en los comentarios del protocolo.

Preguntas frecuentes

¿Por qué delegate se declara como weak?

Weak previene un retain cycle entre el delegado y el objeto delegante. Si la referencia fuera fuerte, los objetos se mantendrían mutuamente y ARC no podría liberarlos. Una referencia weak automáticamente se vuelve nil cuando el delegado se desasigna. Esta es una práctica estándar de Cocoa Touch desde la aparición de Objective-C y se conserva en Swift para compatibilidad hacia atrás.

¿Cuál es la diferencia entre delegate y dataSource?

Delegate maneja eventos y gestiona el comportamiento (altura de celdas, respuesta a toques). DataSource proporciona datos para mostrar (número de filas, celdas). El delegado responde a la pregunta "¿cómo?", dataSource a la pregunta "¿qué?". En iOS, ambos se implementan mediante protocolos, a menudo en el mismo controlador, pero están conceptualmente separados.

¿Se puede usar una struct como delegate?

No, si el protocolo hereda de AnyObject (protocolo de clase). Las referencias weak solo están disponibles para reference types (clases). Para value types (struct, enum), usa callback closures o una clase envoltorio separada. Si controlas el protocolo, puedes evitar heredar AnyObject, pero entonces weak está prohibido — elige conscientemente entre delegate weak y delegate struct.

¿Qué es el método responds(to:) y para qué sirve?

responds(to:) es un método de NSObjectProtocol que verifica si un objeto implementa el selector especificado. Se utiliza para verificar métodos opcionales @objc del protocolo antes de llamarlos. Sin esta verificación, llamar a un método opcional no implementado provocaría NSInvalidArgumentException. En Swift, para protocolos con @objc optional, la verificación puede ser implícita mediante optional binding.

¿Delegate es un singleton o no?

No, delegate es un patrón de delegación, no un singleton. A diferencia de un singleton, un delegado puede reemplazarse en tiempo de ejecución y existe en una única instancia para cada objeto delegante. Un objeto puede ser delegado de varios remitentes. Singleton es un patrón creacional que garantiza una única instancia de clase, lo que no tiene relación con la delegación.

Resumen

  • Delegate es un patrón donde un objeto delega el manejo de eventos a otro objeto a través de un protocolo con métodos tipados.
  • Weak var es obligatorio para la propiedad delegate para prevenir retain cycles y fugas de memoria.
  • Protocol define métodos de delegación obligatorios y opcionales (@objc optional).
  • iOS SDK contiene 15+ protocolos delegate integrados: UITableViewDelegate, URLSessionDelegate, CLLocationManagerDelegate.
  • Delegate es preferible a callback cuando hay 3+ eventos diferentes de una sola fuente (CLLocationManager).
  • Delegate asíncrono se usa en URLSession para recibir datos y progreso sin bloquear el hilo.
  • Establece el delegate antes de iniciar una operación asíncrona y establécelo a nil en deinit para una gestión segura de la memoria.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también