Programación en Swift y SwiftUI para iOS Developers

MVC vs MVVM en iOS y Swift

En el mundo de la programación swift y el desarrollo de aplicaciones para el ecosistema Apple, hay una verdad universal: escribir código es fácil, pero mantenerlo es difícil. Cuando abres un proyecto en Xcode y te encuentras con un archivo de 3,000 líneas donde la lógica de negocio, la interfaz de usuario y las llamadas a la red están mezcladas, sabes que algo salió mal.

Para un iOS developer, entender los patrones de arquitectura no es solo una cuestión académica; es la diferencia entre una app que escala y una que colapsa bajo su propio peso. Hoy vamos a diseccionar los dos gigantes de la arquitectura en iOS: MVC (Model-View-Controller) y MVVM (Model-View-ViewModel), centrándonos especialmente en su implementación con SwiftUI.

Aprenderás qué son, cómo se diferencian y, lo más importante, cómo implementarlos paso a paso para desarrollar aplicaciones robustas en iOS, macOS y watchOS.


¿Por qué necesitamos una arquitectura?

Antes de entrar en el debate de MVC vs MVVM en iOS y Swift, debemos entender el problema. Sin una arquitectura definida, el código tiende a convertirse en “código espagueti”.

Una buena arquitectura busca separar responsabilidades (Separation of Concerns). Queremos que:

  1. La Interfaz (UI) solo se preocupe de pintar cosas en la pantalla.
  2. Los Datos (Model) solo se preocupen de la estructura de la información.
  3. La Lógica conecte ambos mundos sin que se conozcan directamente entre sí.

MVC: El Clásico (Model-View-Controller)

El patrón MVC es el abuelo de las arquitecturas de software y ha sido el estándar recomendado por Apple desde los inicios de iOS con UIKit.

¿Qué es MVC?

El patrón divide la aplicación en tres componentes principales:

  1. Model (Modelo): Representa los datos y la lógica de negocio pura. No sabe nada de la interfaz de usuario. (Ej: Una estructura User o una clase que gestiona una base de datos).
  2. View (Vista): Es lo que el usuario ve. Botones, etiquetas, imágenes. En UIKit, esto eran los archivos .xib o las clases UIView. En SwiftUI, son tus estructuras View. La vista es “tonta”; no sabe manipular datos, solo mostrarlos.
  3. Controller (Controlador): Es el cerebro. Enlaza el modelo con la vista. Recibe eventos del usuario (taps), actualiza el modelo y luego refresca la vista.

El Problema del MVC en iOS: “Massive View Controller”

En la teoría, el diagrama de MVC se ve limpio. En la práctica dentro de Xcode y UIKit, el UIViewController asumía demasiadas responsabilidades. Gestionaba el ciclo de vida de la vista (viewDidLoad), la navegación, las delegaciones de tablas, las llamadas a API y la lógica de negocio.

Esto llevó al chiste recurrente en la comunidad: MVC significa Massive View Controller.

MVC en SwiftUI: Un enfoque diferente

En SwiftUI, el concepto de “Controlador” cambia. Al ser un framework declarativo, la Vista se repinta automáticamente cuando cambia el estado (@State).

En una implementación estricta de MVC en SwiftUI, la propia View (struct) actúa a menudo como Vista y Controlador a la vez, gestionando el estado directamente.

Ejemplo de MVC en SwiftUI (El enfoque simple)

Imaginemos una app que muestra un contador.

import SwiftUI

// MODELO
struct CounterModel {
    var count: Int = 0
}

// VISTA + CONTROLADOR (Todo en uno)
struct CounterMVCView: View {
    // El estado está gestionado aquí mismo (rasgo de Controlador)
    @State private var model = CounterModel()
    
    var body: some View {
        VStack {
            Text("Contador: \(model.count)")
                .font(.largeTitle)
            
            HStack {
                // Lógica de actualización incrustada en la Vista
                Button("Restar") {
                    model.count -= 1
                }
                
                Button("Sumar") {
                    model.count += 1
                }
            }
        }
    }
}

Análisis: Para apps muy pequeñas, esto funciona. Pero si necesitas validar que el contador no baje de cero, o hacer una llamada a una API al llegar a 10, empezarás a llenar la struct View de funciones lógicas, ensuciando el código de interfaz.


MVVM: El Estándar Moderno (Model-View-ViewModel)

Aquí es donde entra MVVM. Este patrón se ha convertido en el favorito para el ios developer moderno, especialmente con la llegada de SwiftUI y el framework Combine (o la nueva macro @Observable).

¿Qué es MVVM?

MVVM nace para solucionar el problema del “Massive View Controller” separando la lógica de presentación de la interfaz.

  1. Model (Modelo): Igual que en MVC. Datos puros.
  2. View (Vista): La interfaz visual (SwiftUI Views). En MVVM, la vista es totalmente pasiva. Solo “observa” al ViewModel y reacciona.
  3. ViewModel (Modelo de Vista): Es el intermediario. Es una clase que contiene la lógica de negocio necesaria para la vista. Transforma los datos del Modelo en valores listos para ser mostrados por la Vista.

La clave de MVVM en SwiftUI: El Binding (Enlace de datos). La Vista se suscribe a los cambios del ViewModel. Si el ViewModel cambia una variable, la Vista se actualiza sola.

Implementando MVVM en Swift y SwiftUI

Vamos a refactorizar el ejemplo anterior y hacerlo más complejo (simulando una carga de usuario) para ver el poder de MVVM en Xcode.

1. El Modelo (Model)

El modelo debe ser simple. Usaremos Structs de Swift.

struct User: Identifiable, Codable {
    let id: UUID
    let name: String
    let role: String
}

2. El ViewModel

Aquí ocurre la magia. El ViewModel debe ser una clase (class) que conforme al protocolo ObservableObject. Esto permite a SwiftUI “escuchar” cambios.

Nota: En iOS 17+ podemos usar la macro @Observable, pero usaremos el estándar ObservableObject para máxima compatibilidad.

import SwiftUI

class UserViewModel: ObservableObject {
    // @Published notifica a la Vista cada vez que esta variable cambia
    @Published var user: User?
    @Published var isLoading: Bool = false
    @Published var errorMessage: String?
    
    // Lógica de Negocio: Simular una llamada de red
    func fetchUser() {
        self.isLoading = true
        self.errorMessage = nil
        
        // Simulamos latencia de red de 2 segundos
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            // Lógica de negocio: Aleatoriedad o validación
            let success = Bool.random()
            
            if success {
                self.user = User(id: UUID(), name: "Carlos Developer", role: "Senior iOS Architect")
            } else {
                self.errorMessage = "Error al conectar con el servidor."
            }
            
            self.isLoading = false
        }
    }
}

3. La Vista (View)

La Vista ahora no tiene lógica compleja. Solo declara su interfaz y usa @StateObject para instanciar su ViewModel.

struct UserProfileView: View {
    // Inyectamos el ViewModel. La Vista es dueña de este estado.
    @StateObject private var viewModel = UserViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Perfil de Usuario")
                .font(.title)
                .bold()
            
            if viewModel.isLoading {
                ProgressView("Cargando datos...")
            } else if let error = viewModel.errorMessage {
                Text(error)
                    .foregroundColor(.red)
                    .padding()
                    .background(Color.red.opacity(0.1))
                    .cornerRadius(8)
            } else if let user = viewModel.user {
                VStack {
                    Image(systemName: "person.circle.fill")
                        .resizable()
                        .frame(width: 100, height: 100)
                        .foregroundColor(.blue)
                    
                    Text(user.name)
                        .font(.headline)
                    Text(user.role)
                        .font(.subheadline)
                        .foregroundColor(.gray)
                }
                .transition(.scale) // Animación nativa de SwiftUI
            } else {
                Text("No hay datos cargados")
                    .foregroundColor(.secondary)
            }
            
            Button("Cargar Usuario") {
                // La Vista delega la acción al ViewModel
                viewModel.fetchUser() // ¡Animación implícita!
            }
            .buttonStyle(.borderedProminent)
            .disabled(viewModel.isLoading)
        }
        .padding()
        .animation(.default, value: viewModel.user) // Anima cambios en el usuario
    }
}

¿Por qué este código es mejor?

  1. Testabilidad: Puedes crear Tests Unitarios para UserViewModel sin tener que instanciar la interfaz gráfica. Puedes probar si fetchUser cambia isLoading a true y luego a false.
  2. Separación: Si el diseñador cambia toda la UI mañana, la lógica del ViewModel no se toca.
  3. Reactividad: SwiftUI brilla cuando el estado dicta la vista. MVVM se alinea perfectamente con esta filosofía.

Comparativa: MVC vs MVVM en iOS y Swift

Para un iOS developer, elegir entre estos dos puede ser confuso. Aquí tienes una tabla comparativa directa:

CaracterísticaMVC (Model-View-Controller)MVVM (Model-View-ViewModel)
DistribuciónLógica y Vista suelen acoplarse.Lógica separada en ViewModel. Vista pasiva.
ComplejidadBaja. Fácil de empezar.Media. Requiere entender Bindings/Observables.
TestabilidadDifícil (la lógica está en la UI).Alta (ViewModel es código puro sin UI).
Tamaño de ArchivosTiende a controladores gigantes.Archivos más pequeños y enfocados.
Sinergia con SwiftUIPobre (lucha contra el framework).Excelente (nativo con @Published).

Escalando a Multiplataforma: iOS, macOS y watchOS

Una de las ventajas más potentes de usar MVVM y SwiftUI en Xcode es la capacidad de reutilizar código.

Imagina que quieres llevar tu app de iOS a watchOS.

Con MVC, tendrías que reescribir casi todo el Controlador porque UIViewController (iOS) es diferente de WKInterfaceController (watchOS antiguo) o la gestión de vistas.

Con MVVM, tu UserViewModel es 100% reutilizable. Es código Swift puro, no depende de librerías de interfaz (UIKit o AppKit). Solo importas Foundation y Combine (o SwiftUI para los wrappers de propiedades).

Ejemplo de reutilización en watchOS

Simplemente creas una nueva Vista específica para el reloj, pero usas el mismo ViewModel.

// Target: watchOS App
import SwiftUI

struct WatchUserProfileView: View {
    // Reutilizamos el MISMO ViewModel que en iOS
    @StateObject private var viewModel = UserViewModel()
    
    var body: some View {
        ScrollView {
            VStack {
                if viewModel.isLoading {
                    ProgressView()
                } else if let user = viewModel.user {
                    // Diseño simplificado para pantalla pequeña
                    Text(user.name)
                        .font(.caption)
                        .bold()
                    Text(user.role)
                        .font(.system(size: 10))
                }
                
                Button(action: { viewModel.fetchUser() }) {
                    Image(systemName: "arrow.clockwise")
                }
            }
        }
    }
}

Esto es el “Santo Grial” del desarrollo multiplataforma nativo de Apple. Escribes la lógica una vez (Modelo + ViewModel) y solo adaptas la Vista para cada dispositivo (iOS, iPadOS, macOS, watchOS, tvOS).


Consejos Pro para el iOS Developer en MVVM

Al implementar MVVM en tus proyectos de programación swift, ten en cuenta estos consejos avanzados:

1. No conviertas el ViewModel en un “Massive ViewModel”

Es fácil caer en el mismo error que en MVC. Si tu ViewModel crece mucho, divídelo. Puedes tener ViewModels hijos o servicios separados (por ejemplo, APIService, AuthService) que el ViewModel llama.

2. Dependency Injection (Inyección de Dependencias)

Para que tu arquitectura sea verdaderamente profesional, no instancies los servicios dentro del ViewModel. Inyéctalos.

Mal:

class UserViewModel: ObservableObject {
    let api = APIService() // Acoplamiento fuerte
}

Bien (Profesional):

class UserViewModel: ObservableObject {
    let api: APIServiceProtocol
    
    init(api: APIServiceProtocol = APIService()) {
        self.api = api
    }
}

Esto te permite inyectar un “MockAPIService” falso cuando ejecutas tests unitarios, simulando respuestas del servidor sin internet.

3. @StateObject vs @ObservedObject

Este es un error común en entrevistas de Swift.

  • Usa @StateObject cuando la Vista crea el ViewModel por primera vez (es la dueña).
  • Usa @ObservedObject cuando la Vista recibe un ViewModel que ya fue creado en otra pantalla padre.

Si usas @ObservedObject para crear el ViewModel, este se reiniciará cada vez que la vista se redibuje, perdiendo tus datos.


Conclusión: ¿Cuál deberías usar?

Si estás empezando en la programación swift con proyectos muy pequeños o prototipos desechables, MVC (o simplemente poner el estado en la Vista) es aceptable por velocidad.

Sin embargo, para cualquier desarrollo profesional, y especialmente si estás buscando trabajo como ios developer, MVVM es el camino a seguir. Es la arquitectura que mejor se adapta a la naturaleza declarativa de SwiftUI. Te permitirá:

  1. Escribir código más limpio y legible en Xcode.
  2. Testear tu lógica de negocio fácilmente.
  3. Llevar tu app a macOS o watchOS reutilizando el 80% de tu código.

Dominar MVC vs MVVM en iOS y Swift es un hito fundamental en tu carrera. No se trata solo de saber escribir código, sino de saber organizarlo para que tu “yo del futuro” (o tus compañeros de equipo) te lo agradezcan.

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

Swift Charts en SwiftUI

Next Article

Cómo crear un juego con SpriteKit en SwiftUI

Related Posts