Swift Concurrency Series 07|Errores comunes al combinar SwiftUI con async/await
El verdadero problema no suele estar en la sintaxis, sino en si el "ciclo de vida de la página" y el "ciclo de vida de la tarea" están alineados.
SwiftUI y async/await son elegantes cuando se ven individualmente, pero cuando se combinan, el problema más fácilmente expuesto es el ciclo de vida.
Más precisamente, es la dislocación entre los dos ciclos de vida:
- ¿Cuándo aparece, se vuelve a dibujar o desaparece una página de SwiftUI?
- ¿Cuándo comienza, se pausa, finaliza y se cancela una tarea asincrónica?
Si estas dos cosas no están alineadas, incluso si el código se puede ejecutar hoy, crecerá fácilmente en el futuro:
- Repetir solicitud
- Página parpadeante
- Vuelve a escribir resultados antiguos
- El estado de carga es confuso.
- La tarea aún se actualiza después de salir de la página.
Entonces, de lo que realmente quiere hablar este artículo es: las páginas asincrónicas de SwiftUI son propensas al caos y cuáles son las causas fundamentales detrás de este caos.
1. El error de juicio más común: tratar la Vista como un objeto estable
Este es el hábito más fácil de incorporar para muchos desarrolladores con experiencia en UIKit.
Todos usarán por defecto:
- La página aparece una vez.
- Solicitud de envío una vez
- Actualizar la página actual después de que regrese la solicitud.
Pero View en SwiftUI se parece más a una descripción de estado que a una instancia estable que puede conservarse durante mucho tiempo.
Esto significa que si el valor predeterminado en su mente es “la Vista frente a mí siempre estará ahí, por lo que esta tarea naturalmente le pertenece”, será fácil causar problemas más adelante.
SwiftUI no requiere que las tareas estén vinculadas a un objeto estable, simplemente es fácil engañarse pensando que ya lo ha hecho.
2. El mayor problema de onAppear es que se utiliza como entrada de inicialización única.
Muchos artículos dirán: onAppear se puede ejecutar varias veces.
Esta afirmación es cierta, pero no es suficiente.
El verdadero peligro es que a menudo se escribe como la entrada estándar de facto para “inicialización de página”:
.onAppear {
Task {
await loadData()
}
}
El problema no es que este código deba estar equivocado, sino que es fácil añadirlo mentalmente:
“Cuando aparezca esta página, sólo se ejecutará una vez”.
Una vez que pienses de esta manera, aparecerá lo siguiente uno tras otro:
- Repetir solicitud
- Restablecer estado repetidamente
- Enterrar puntos repetidamente
- Los datos se borraron justo después de mostrarse y comenzaron de nuevo.
Entonces una idea más estable es: **Deje que el proceso asincrónico en sí sea idempotente, deduplicado o reemplazable. **
3. El segundo error: mezclar el estado de la página y el estado de la tarea
Los estados comunes en una página SwiftUI incluyen:
- Criterios de filtro actuales
- Contenido de datos actuales
- ¿Se está cargando?
- mensaje de error
- Tareas actualmente en ejecución
Si estos estados no están claramente estratificados, pueden fácilmente agruparse.
Los malos olores más comunes son:
itemsisLoadingerrorisRefreshingkeywordselectedTab
Cada valor individualmente tiene sentido, pero es difícil saber cómo se relacionan entre sí. Entonces la página entrará en un estado muy incómodo:
- Parece que está en cualquier condición.
- Pero ninguno de los estados expresa realmente “en qué semántica se encuentra ahora la página”
En este caso, tan pronto como vuelva el resultado asincrónico, cualquier estado puede cambiarse y el problema sólo quedará expuesto tarde o temprano.
4. El tercer error: los resultados antiguos se vuelven a escribir en la interfaz de usuario actual
Este es uno de los problemas más frecuentes en las páginas asincrónicas de SwiftUI.
Los escenarios típicos incluyen:
- Los usuarios pueden cambiar de pestaña rápidamente
- Las palabras clave de búsqueda cambian continuamente
- Cambiar repetidamente las condiciones del filtro.
- La página activa la actualización y la primera carga, una tras otra.
En la superficie, puede pensar que simplemente “envió varias tareas”, pero de hecho, el verdadero problema es:
**Aunque la tarea anterior todavía está completada legalmente, ya no corresponde al estado actual de la página. **
Una vez que los resultados antiguos aún se pueden escribir en la interfaz de usuario actual, la apariencia que ve suele ser simplemente:
- La página parpadea
- La lista retrocede repentinamente.
- El estado de carga termina repentinamente
- Aparece un mensaje de error inexplicablemente.
Estos fenómenos son muy similares a los “pequeños errores”, pero las causas fundamentales son muy similares: No se gestiona la validez de los resultados de la tarea.
5. El cuarto pozo: difundir todas las entradas asincrónicas en la Vista
Si estas entradas aparecen en una página al mismo tiempo:
onAppear { Task { ... } }refreshable { await ... }onChange(of:) { Task { ... } }- Haga clic en el botón para abrir otro
Task
Todos ellos parecen legítimos individualmente, pero en conjunto rápidamente se convierten en un problema:
**La relación de tareas está fuera de control. **
Cada vez es más difícil responder:
- ¿Quién es la entrada principal?
- ¿Quién debería cancelar a quién?
- ¿Quién está calificado para cambiar el estado de visualización actual?
- ¿A qué ronda de tareas corresponde una determinada actualización de estado?
Por lo tanto, muchas páginas asincrónicas de SwiftUI tienen demasiadas entradas, cada entrada puede activar tareas directamente y, finalmente, no existe una capa de coordinación unificada.
6. El quinto error: el valor predeterminado es “siempre que la interfaz de usuario pueda actualizarse”
SwiftUI oculta muchos detalles de actualización de la interfaz de usuario, lo que da a las personas la ilusión de que, siempre que finalmente cambie el estado, la página se actualizará de forma natural.
Pero la verdadera pregunta no es “¿se actualizará?” pero “si está calificado para actualizarse en este momento”.
Por ejemplo:
- ¿Ha caducado el resultado actual?
- ¿La página actual sigue viva?
- ¿El estado actual sigue coincidiendo con esta ronda de tareas?
- ¿La modificación actual debería realizarse bajo la semántica del actor principal?
Si estas cosas no se toman en serio, es posible que la página no se bloquee de inmediato, pero poco a poco acumulará mucha “confusión accidental”.
7. Un enfoque más estable: dejar que la Vista active la intención y dejar que la capa de estado administre la tarea
El método de organización que prefiero es:
- Ver es responsable de expresar la intención del usuario.
- ViewModel o capa de estado es responsable de gestionar las tareas y las calificaciones de los resultados.
- Ver sólo consume el estado ordenado
Dicho esto, es mejor que la Vista sepa menos sobre estos detalles:
- Si cancelar tareas antiguas
- ¿Qué resultado ha caducado?
- ¿La carga actual se está cargando por primera vez o se está actualizando?
- Si el estado de error debería sobrescribir el contenido antiguo
Si estos problemas se dejan en la Vista, la página cambiará rápidamente de “UI declarativa” a “shell declarativo envuelto en una capa de lógica asincrónica descentralizada”.
8. Una sugerencia más cercana al combate real.
Si una página de SwiftUI comienza a complicarse, normalmente me obligo a responder primero las siguientes preguntas:
- ¿Qué entradas de tareas hay en la página?
- Si coexisten, reemplazan o ignoran tareas similares.
- Qué estados son estados semánticos de página y cuáles son simplemente estados de proceso interno.
- Si todavía se permite cambiar los resultados anteriores a la página actual.
- Después de salir de la página, qué tareas se deben continuar y cuáles se deben detener.
Una vez que no pueda responder estas preguntas, generalmente significa que el modelo de tarea de la página aún no se ha establecido.
9. Conclusión: la verdadera dificultad de las páginas asincrónicas de SwiftUI es la alineación del ciclo de vida
Una situación común es que los principales problemas entre SwiftUI y async/await son la sintaxis.
Pero la situación más real es:
Viewno es un objeto estable como se podría pensar en la superficie.onAppearno es una semántica de inicialización única- Los resultados antiguos no caducan automáticamente porque han “caducado”
- Si hay demasiadas entradas de tareas a nivel de página, definitivamente comenzará a complicarse.
Entonces, la página asincrónica de SwiftUI verdaderamente estable es:
Primero alinee el ciclo de vida de la página con el ciclo de vida de la tarea y luego escriba código asincrónico específico.
Sólo si esto se establece de antemano, la ligereza de SwiftUI y la elegancia de async/await se convertirán realmente en ventajas, en lugar de hacer que el caos sea más fácil de escribir.
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #SwiftUI?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home