Métodos de clase en Swift: static vs class

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

Resumen

Domina los métodos de clase en Swift con un ejemplo práctico: un LevelTracker que desbloquea niveles y un Player que avanza. Aquí verás cuándo usar static o class, cómo separar lógica compartida de la lógica por instancia y por qué mutating e init son clave para un sistema de niveles robusto.

¿Qué es un método de clase en Swift y cuándo usar static o class?

Un método es de clase cuando lo invoca la propia clase y no una instancia. La sintaxis es similar a las properties de tipo: antepones class o static a la declaración del método. Usa class si quieres permitir override en subclases; usa static si no quieres que se sobreescriba. En una estructura, no puedes usar class: solo static.

  • Un método de clase se llama con el nombre del tipo, no con una instancia.
  • class permite herencia en clases. static bloquea la sobreescritura.
  • En struct, solo se permite static.

¿Cómo se invoca un método de clase?

Ejemplo simple con print para mostrar la llamada desde el tipo.

class SomeClass {
    class func someMethod() {
        print("Hola")
    }
    static func anotherMethod() {
        print("Hola")
    }
}

SomeClass.someMethod() // Invocado por la clase, no por una instancia.

¿Cómo funciona el LevelTracker con métodos estáticos?

Se define una estructura LevelTracker con estado compartido y de instancia. El estado global se maneja con propiedades estáticas y la lógica común con métodos estáticos. Para cambiar el nivel actual del jugador se usa un método mutating porque modifica la instancia.

  • highestUnlockedLevel: nivel máximo desbloqueado, compartido por todos los jugadores.
  • currentLevel: nivel actual de una instancia concreta.
  • unlock(level:): método estático para desbloquear niveles globalmente.
  • isUnlocked(level:): método estático que valida si un nivel está disponible.
  • advance(to:): método de instancia y mutating para actualizar el nivel del jugador.
struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(level: Int) {
        if level > highestUnlockedLevel {
            highestUnlockedLevel = level
        }
    }

    static func isUnlocked(level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level: level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

¿Qué reglas se aplican a static y mutating?

  • No mezcles llamadas estáticas e instancia sin el tipo: usa el nombre del tipo para acceder a lo estático.
  • En struct no se usa class: solo static para métodos y propiedades de tipo.
  • Marca como mutating los métodos que cambian stored properties de la instancia.

¿Cómo integrar Player y completar niveles?

Player compone un LevelTracker y un nombre. Como hay una stored property sin valor inicial, necesitas un inicializador. Para completar un nivel, primero se desbloquea el siguiente con el método estático y luego se avanza con el método de instancia.

  • tracker: instancia de LevelTracker.
  • playerName: nombre del jugador.
  • init(name:): obliga a inicializar playerName.
  • complete(level:): desbloquea y avanza al siguiente nivel.
class Player {
    var tracker = LevelTracker()
    var playerName: String

    init(name: String) {
        self.playerName = name
    }

    func complete(level: Int) {
        // Desbloquear el siguiente nivel de forma global.
        LevelTracker.unlock(level: level + 1)
        // Avanzar el nivel del jugador.
        _ = tracker.advance(to: level + 1)
    }
}

var player = Player(name: "Juan Gabriel")
print(player.tracker.currentLevel) // 1

player.complete(level: 1)
print(player.tracker.currentLevel) // 2

if player.tracker.advance(to: 7) {
    print("Podemos avanzar hasta el nivel siete")
} else {
    print("El nivel siete sigue bloqueado por ahora")
}

LevelTracker.unlock(level: 7)
if player.tracker.advance(to: 7) {
    print("Podemos avanzar hasta el nivel siete")
}

¿Por qué hace falta un inicializador?

  • Si una stored property no tiene valor por defecto, debes asignarla en init.
  • Un fix común es dar un valor vacío, pero es preferible un init(name:) claro.

¿Te gustaría extender este patrón con habilidades, experiencia o logros? Comparte tus ideas y casos de uso en los comentarios.