Ingeniería iOS

[Arquitectura iOS #3] ¿SwiftUI necesita ViewModel? Debate MV

En la comunidad iOS hay un intenso debate desde hace años.

5 min de lectura
Imagen de portada de [Arquitectura iOS #3] ¿SwiftUI necesita ViewModel? Debate MV

En la comunidad iOS hay un intenso debate desde hace años.

«¿SwiftUI necesita ViewModel?»

En la entrega anterior resumimos que el núcleo de MVVM es el enlace, pero SwiftUI lo incorpora en el propio framework. Entonces surgió la pregunta: «¿No hace redundante la propia capa ViewModel?». Es la postura del llamado patrón MV (Model-View).

En esta última entrega de la serie, revisaremos de forma equilibrada los argumentos de ambos lados y resumiremos con qué criterios decidir en la práctica.


La View de SwiftUI ya se parece a un ViewModel

Si recordamos por qué MVVM era necesario en UIKit, la respuesta es esta.

  • UIViewController crecía demasiado, así que había que extraer el estado y la lógica
  • Hacía falta un enlace para sincronizar el estado con la pantalla

Pero la View de SwiftUI es, desde el principio, una función del estado. bodySolo recibe el estado y devuelve una declaración de la pantalla; cuando @Statecambian los valores, el framework vuelve a renderizarla automáticamente. No hace falta escribir código de suscripción a Combine para el enlace.

Además, la View de SwiftUI no es una clase, sino un struct de tipo por valor. No es una masa de ciclo de vida que pueda crecer hasta miles de líneas como UIViewController, sino más bien un plano ligero que se crea de nuevo en cada renderizado.

El argumento del bando MV parte de aquí: «Si la View no se vuelve enorme y el enlace es gratuito, ¿crear una clase ViewModel para cada pantalla no es conservar por inercia una costumbre de UIKit?».


Argumentos del bando MV

En el patrón MV no se crea un ViewModel para cada pantalla; la View usa directamente las herramientas de estado.

  • El estado limitado a una sola vista se mantiene @Statelocalmente
  • El estado compartido por varias pantallas se inyecta @Observablecomo@Environment modelo
  • Los datos del servidor los gestiona el store o client de la capa Model
struct ProfileView: View {
    @Environment(UserStore.self) private var store
    @State private var isEditing = false

    var body: some View {
        List(store.posts) { PostRow(post: $0) }
            .task { await store.loadPosts() }
    }
}

Los ejemplos oficiales de Apple, como Fruta y Food Truck, siguen en general esta estructura. En lugar de un ViewModel por pantalla, varias View comparten un único @Observablemodelo de dominio.

La ventaja es clara: desaparece el código repetitivo de crear una clase por pantalla y se pueden usar directamente herramientas del framework como @State y @Binding, sin rodeos.

La View de SwiftUI es, desde el principio, una función del estado
La View de SwiftUI es, desde el principio, una función del estado

La réplica del bando MVVM

Los argumentos del otro lado tampoco son débiles.

Primero, la lógica se filtra en la View. Cuando condiciones como «si hay cero publicaciones, terminó la carga y no hay errores, muestra un mensaje informativo» empiezan a acumularse como operadores ternarios dentro de body, la lectura sigue siendo difícil aunque sea un tipo por valor.

Segundo, el problema de las pruebas. El body de la View es difícil de ejecutar mediante pruebas unitarias. Si la lógica de presentación está dentro de la View, también queda fuera del alcance de las pruebas. Al extraerla a un ViewModel, se puede validar directamente como un objeto Swift puro.

Tercero, el problema de la escala. En una app con decenas de pantallas y varios desarrolladores, hace falta una regla coherente sobre «dónde están el estado y la lógica». La postura es que reservar un lugar fijo llamado ViewModel para cada pantalla reduce el coste de colaboración.


No es el nombre, sino la dirección de las dependencias

Al comparar ambos argumentos, el debate no trata tanto de «si existe una clase ViewModel» como de dónde viven la lógica y el estado. Con este criterio, la mayoría de los casos se aclaran.

  • El estado de UI limitado a una sola vista (toggle, foco o visibilidad de una sheet) se mantiene @Stateen la View. Extraerlo a un ViewModel sería sobrediseño.
  • El estado de dominio compartido por varias pantallas se convierte en un @Observablemodelo y se inyecta. Ya se llame Store o ViewModel, la esencia es la misma.
  • Cuando la lógica de transformación para la pantalla se complica (formateo, condiciones de presentación, ordenación y filtrado), se extrae a un tipo comprobable fuera de la View. Eso es, en la práctica, un ViewModel.
  • En cualquier caso, mientras se mantenga la dirección de dependencias y se evite que la View acceda directamente a la red o a la base de datos, la diferencia entre MV y MVVM es principalmente nominal.

En resumen, la realidad de «SwiftUI no necesita ViewModel» puede expresarse así: no hace falta crear ViewModel mecánicamente para cada pantalla. Pero eso no significa que desaparezca la lógica que debe extraerse de la View.

El criterio real no es el nombre, sino la dirección de las dependencias
El criterio real no es el nombre, sino la dirección de las dependencias

Resumen de la serie

Si condensamos en una línea cada parte de esta serie de arquitectura iOS, queda así.

  • MVC: UIViewController combina View y Controller, por lo que concentra demasiadas responsabilidades. Sigue siendo válido para pantallas pequeñas.
  • MVVM: extrae el estado de la pantalla a un ViewModel y lo sincroniza mediante enlaces. Sin enlaces, queda incompleto.
  • Debate MV: como SwiftUI incluye enlaces, no hay razón para imponer un ViewModel por pantalla. Aun así, hay que diseñar dónde vive la lógica y cuál es la dirección de las dependencias.

La arquitectura no es una moda, sino una elección de trade-offs adecuada al equipo y al tamaño de la app. Si la respuesta es positiva, también abordaremos opciones más pesadas como VIPER y TCA.

Artículos relacionados