Programación en Swift y SwiftUI para iOS Developers

@discardableResult en Swift

Como iOS Developer, seguramente te has encontrado con esa molesta advertencia amarilla en Xcode que dice: “Result of call to function returning ‘Type’ is unused”. En la programación Swift, el compilador es estrictamente seguro por diseño; si una función devuelve un valor, Swift espera que hagas algo con él. Sin embargo, hay ocasiones en las que ese valor de retorno es un “extra” opcional y no el propósito principal de la llamada a la función.

¿Cómo le decimos a Xcode que está bien ignorar ese valor sin llenar nuestro código de asignaciones inútiles como let _ = miFuncion()? Aquí es donde entra en juego el atributo @discardableResult en Swift.

En este extenso tutorial, aprenderás exactamente qué es, cómo implementarlo, y cuáles son las mejores prácticas para usarlo al desarrollar aplicaciones con Swift y SwiftUI para iOS, macOS y watchOS.


1. ¿Qué es @discardableResult en Swift?

En versiones antiguas de Swift (antes de Swift 3), los valores de retorno de las funciones podían ser ignorados por defecto. Si querías obligar al desarrollador a usar el resultado, tenías que añadir un atributo llamado @warn_unused_result.

Apple invirtió este comportamiento porque la omisión de resultados a menudo conducía a errores lógicos graves (por ejemplo, llamar a un método que devuelve una cadena modificada pero no muta la original, y no guardar el resultado). Hoy en día, en la moderna programación Swift, el comportamiento predeterminado exige que uses el valor de retorno.

El atributo @discardableResult es simplemente un modificador que colocas justo antes de la declaración de una función o método. Su único propósito es decirle al compilador de Swift: “Oye, esta función devuelve un valor, pero es completamente seguro y aceptable si el desarrollador decide ignorarlo”.

El Problema: Advertencias en Xcode

Imagina que tienes una clase que gestiona una base de datos local. Tienes una función eliminarUsuario que elimina el registro y devuelve un booleano indicando si la operación fue exitosa.

class DatabaseManager {
    func eliminarUsuario(id: Int) -> Bool {
        // Lógica de eliminación...
        print("Usuario \(id) eliminado")
        return true
    }
}

let db = DatabaseManager()
db.eliminarUsuario(id: 42) 

Si escribes esto en Xcode, inmediatamente verás una advertencia en la línea donde llamas a la función.

Ignorar las advertencias en Xcode es una mala práctica para cualquier iOS Developer, ya que oculta posibles problemas reales debajo de un mar de alertas amarillas.


2. Cómo y Cuándo Usar @discardableResult en Swift

La solución al problema anterior es extremadamente sencilla. Solo necesitas añadir el atributo antes de la palabra clave func:

class DatabaseManager {
    @discardableResult
    func eliminarUsuario(id: Int) -> Bool {
        // Lógica de eliminación...
        print("Usuario \(id) eliminado")
        return true
    }
}

let db = DatabaseManager()
db.eliminarUsuario(id: 42) // ¡Cero advertencias en Xcode!

Pero la verdadera pregunta no es cómo usarlo (es solo una palabra), sino cuándo es arquitectónicamente correcto usarlo. Veamos los casos de uso más profesionales.

2.1. Funciones de Mutación que Devuelven el Objeto Antiguo

Es muy común en la programación Swift tener funciones que alteran una colección o un estado y, por conveniencia, devuelven el valor que acaba de ser sobrescrito o eliminado.

struct PilaDeNavegacion {
    private var vistas: [String] = []
    
    mutating func push(_ vista: String) {
        vistas.append(vista)
    }
    
    @discardableResult
    mutating func pop() -> String? {
        return vistas.popLast()
    }
}

A veces, al hacer un pop() de una vista en iOS o macOS, necesitas saber qué vista fue eliminada para registrarla en un sistema de analíticas. Otras veces, simplemente quieres volver atrás y no te importa el valor eliminado. @discardableResult permite ambos usos de manera limpia.

2.2. El Patrón Builder (Builder Pattern)

Este es el caso de uso estrella para @discardableResult en Swift. El patrón Builder se utiliza para construir objetos complejos paso a paso. Se popularizó mucho en UIKit y sigue siendo muy útil en arquitecturas de la capa de lógica, incluso si usas SwiftUI para la vista.

class URLRequestBuilder {
    private var url: String = ""
    private var method: String = "GET"
    private var headers: [String: String] = [:]
    
    init(baseURL: String) {
        self.url = baseURL
    }
    
    @discardableResult
    func setMethod(_ method: String) -> URLRequestBuilder {
        self.method = method
        return self
    }
    
    @discardableResult
    func addHeader(key: String, value: String) -> URLRequestBuilder {
        self.headers[key] = value
        return self
    }
    
    func build() -> URLRequest? {
        // Construye y retorna la petición final
        guard let validURL = URL(string: url) else { return nil }
        var request = URLRequest(url: validURL)
        request.httpMethod = self.method
        request.allHTTPHeaderFields = self.headers
        return request
    }
}

Gracias a que devolvemos self y usamos @discardableResult, un iOS Developer puede usar esta clase encadenando métodos de forma fluida:

// Uso encadenado (Fluent Interface)
let request = URLRequestBuilder(baseURL: "https://api.ejemplo.com")
    .setMethod("POST")
    .addHeader(key: "Auth", value: "Bearer Token")
    .build()

Si no usáramos @discardableResult, el compilador se quejaría en cada paso del encadenamiento o si decidimos configurar el builder línea por línea sin encadenar.


3. Aplicando @discardableResult en el Ecosistema Apple (iOS, macOS, watchOS)

Una de las grandes ventajas de Swift es que la lógica de negocio es idéntica en todas las plataformas. El uso de @discardableResult te ayudará a crear frameworks y paquetes (Swift Packages) que sean agradables de consumir independientemente del dispositivo destino.

3.1. En SwiftUI: Gestores de Estado y ViewModels

Aunque SwiftUI es declarativo y utiliza modificadores de vista que ya implementan conceptos similares, en la capa de tus ObservableObject o modelos de dominio, el uso de este atributo es crucial.

Imagina un ViewModel para una app de Apple Watch (watchOS) que rastrea los entrenamientos:

import SwiftUI

class WorkoutViewModel: ObservableObject {
    @Published var caloriesBurned: Double = 0.0
    
    @discardableResult
    func addActivity(minutes: Double, intensity: Double) -> Double {
        let addedCalories = minutes * intensity * 5.0
        self.caloriesBurned += addedCalories
        return addedCalories
    }
}

En tu vista de SwiftUI, podrías tener un botón que simplemente llama a la función sin importar los cálculos internos de esa sesión específica:

struct WorkoutView: View {
    @StateObject var viewModel = WorkoutViewModel()
    
    var body: some View {
        VStack {
            Text("Calorías: \(viewModel.caloriesBurned)")
            Button("Entrenar 10 min") {
                // No nos importa el valor retornado aquí, solo queremos que el total se actualice
                viewModel.addActivity(minutes: 10, intensity: 1.2)
            }
        }
    }
}

Si un desarrollador luego necesita usar el retorno para mostrar una animación de “¡Quemaste X calorías!”, la función ya está preparada para devolver el valor sin reescribir código.

3.2. Operaciones de CoreData y Bases de Datos (iOS & macOS)

En aplicaciones pesadas de macOS o iOS, es común tener funciones de guardado en disco.

class DataStore {
    @discardableResult
    func saveContext() -> Error? {
        do {
            try context.save()
            return nil // Sin errores
        } catch {
            return error
        }
    }
}

En flujos críticos, comprobarás el error retornado. En autoguardados en segundo plano al cerrar la app, puede que simplemente llames a saveContext() sin asignar su resultado.


4. Mejores Prácticas: Cuándo NO usar @discardableResult

Como ocurre con cualquier herramienta potente en Xcode, puede ser abusada. Un buen iOS Developer sabe cuándo no usarla.

  1. No lo uses para ocultar mala arquitectura: Si tu función principal es calcular algo y no tiene efectos secundarios (side effects), el valor de retorno NUNCA debería ser descartable. Por ejemplo, una función sumar(a: Int, b: Int) -> Int no tiene sentido con @discardableResult. Si ignoras el resultado, la llamada a la función fue una pérdida de CPU.
  2. Alertas Críticas: Si la función devuelve un código de estado o un error que es crítico para la integridad de los datos, obliga al desarrollador a manejarlo. No le pongas el atributo, deja que Xcode le grite si no procesa el posible error de una pasarela de pago, por ejemplo.
  3. Funciones Puras: Si el método no muta el estado interno del objeto, usar @discardableResult es un anti-patrón.

5. Conclusión

Dominar los atributos de compilación como @discardableResult en Swift es lo que diferencia a un programador novato de un verdadero iOS Developer Senior. Te permite diseñar APIs y clases que son flexibles, limpias y expresivas, sin comprometer la seguridad que la programación Swift nos garantiza.

Al desarrollar para el ecosistema de Apple, ya sea una app compleja de gestión de ventanas en macOS, una interfaz fluida en SwiftUI para iOS, o una app ligera para watchOS, utilizar correctamente las indicaciones del compilador en Xcode mantendrá tu base de código libre de advertencias y tu mente enfocada en crear grandes experiencias de usuario.

Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Dynamic Type en SwiftUI

Next Article

EditButton en SwiftUI

Related Posts