Constructores y manejo de inicialización opcional en Swift

Clase 21 de 27Curso de Programación Orientada a Objetos en Swift

Resumen

Comprende con claridad cómo y cuándo usar inicializadores fallables en Swift para evitar estados inválidos. Verás cómo se combinan optionals, init?, validaciones con switch, y el encadenamiento con super.init? en herencia. Todo con ejemplos directos y prácticos.

¿Qué es un inicializador fallable en Swift y cuándo usar optionals?

Un inicializador fallable se marca con init? y expresa que la creación de una instancia puede fallar y devolver nil. Es clave cuando no existe un valor válido para inicializar. Si la inicialización no es posible, el resultado debe ser nil.

  • Usa init? cuando no hay un valor por defecto razonable.
  • Un objeto creado con init? es un optional. Se debe manejar con if let, guard let o un force unwrapping consciente.
  • Si un inicializador puede devolver nil, esta posibilidad se propaga a quien lo usa.

Recuerda: los optionals requieren manejo seguro. Puedes usar if let, guard let o, si sabes que hay valor, un force unwrapping.

¿Cómo crear un enum con init? que valida un Character?

Cuando un valor externo no siempre es válido, init? evita estados imposibles. Ejemplo: un enumerado de unidades de temperatura que se inicializa desde un símbolo (Character). Si el símbolo no coincide, devuelve nil.

enum TemperatureUnit {
    case kelvin, celsius, fahrenheit

    init?(symbol: Character) {
        switch symbol {
        case "k": self = .kelvin
        case "c": self = .celsius
        case "f": self = .fahrenheit
        default: return nil
        }
    }
}

// Uso
let someUnitK = TemperatureUnit(symbol: "k")   // .kelvin
let someUnitX = TemperatureUnit(symbol: "x")   // nil (optional)

Claves del patrón: - Validación con switch sobre el símbolo. - default: return nil cuando no hay caso válido. - El resultado es un optional, así que se aplica la teoría de optionals para consumirlo.

¿Cómo se propagan los optionals con super.init? en herencia?

Si un inicializador puede fallar, y otro inicializador depende de él, la posibilidad de fallo se encadena. Observa un caso típico con un Product y un Cart item.

class Product {
    let name: String

    init?(name: String) {
        if name.isEmpty { return nil } // sin nombre, no hay producto
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int

    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil } // cantidades inválidas
        self.quantity = quantity
        super.init?(name: name) // puede fallar si name está vacío
    }
}

// Consumo seguro con optionals
if let someSocks = CartItem(name: "socks", quantity: 2) {
    print(someSocks.name)      // "socks"
    print(someSocks.quantity)  // 2
}

Puntos importantes: - Validación previa: quantity < 1 implica return nil. - Uso de la propiedad isEmpty en String para validar name. - Llamar a super.init? porque el inicializador del padre también puede fallar. - El resultado final es un optional. Maneja el valor con if let o guard let.

Buenas prácticas al trabajar con init?: - Valida temprano y devuelve nil si falta un dato esencial. - Mantén las reglas de negocio cerca del init? para evitar estados inválidos. - Documenta con claridad por qué el init? puede fallar.

¿Tienes un caso real con init?, if let o guard let que quieras revisar? Cuéntalo en los comentarios y lo analizamos juntos.