Jetpack Compose serie de exploración en profundidad 1: función componible

El significado de las funciones componibles

Si solo nos enfocamos en la sintaxis simple, cualquier función estándar de Kotlin puede convertirse en una función componible al anotarla como @Composable:

inserte la descripción de la imagen aquí

Al hacer esto, esencialmente le estamos diciendo al compilador que la función tiene la intención de convertir algunos datos en un Nodo para registrarlos en el árbol componible. Es decir, si pensamos en las funciones componibles como @Composable (Input) -> Unit, la entrada son datos, pero la salida no es lo que la mayoría de la gente considera como un retorno de función, sino una acción de registro que inserta un elemento en el árbol. Podemos pensar en esto como un efecto secundario de la ejecución de funciones.

La denominada " acción de registro " aquí suele denominarse " emisión " en Compose . La acción de emisión se realiza cuando se ejecuta la función componible, lo que ocurre durante la composición.

inserte la descripción de la imagen aquí
Las funciones componibles se ejecutan con el único propósito de construir o actualizar el estado de representación en memoria del árbol. Esto lo mantendrá siempre actualizado con la estructura de árbol que representa, ya que la función componible se volverá a ejecutar cada vez que cambien los datos que lee. Para mantenerse actualizado con el estado del árbol, pueden realizar operaciones para insertar nuevos nodos (como se indicó anteriormente), pero igualmente pueden eliminar, reemplazar o mover nodos. Las funciones componibles también pueden leer o escribir estados en el árbol.

Propiedades de funciones componibles

Anotar una función como Composabletiene otras implicaciones relacionadas. @ComposableUna anotación cambia efectivamente el tipo de función o expresión a la que se aplica y, como cualquier otro tipo, le impone algunas restricciones o propiedades. Estas propiedades están muy relacionadas con Jetpack Compose, ya que desbloquean la funcionalidad relacionada de la biblioteca de Compose.

Compose runtimeSe espera Composableque una función obedezca las propiedades anteriores, por lo que puede asumir ciertos comportamientos y aprovechar diferentes optimizaciones de tiempo de ejecución, como composición paralela, orden de composición arbitrario basado en prioridad, reorganización inteligente o memoria de ubicación, etc.

En general, la optimización del tiempo de ejecución solo es posible cuando el tiempo de ejecución tiene cierta certeza sobre el código que necesita ejecutar, por lo que puede asumir condiciones y comportamientos específicos a partir de él. Esto desbloquea oportunidades para la ejecución, o en otras palabras, para "consumir" este código con el determinismo antes mencionado para seguir una estrategia de ejecución o técnica de evaluación diferente.

Un ejemplo de estas certezas son las relaciones entre los diferentes elementos del código. ¿Dependen unos de otros?¿Podemos ejecutarlos en paralelo o en un orden diferente sin afectar el programa? ¿Podemos interpretar cada fragmento lógico atómico como una unidad completamente aislada?

contexto de llamada

La mayoría de las propiedades de las funciones componibles están habilitadas por el compilador Compose . Dado que es un complemento del compilador de Kotlin , se ejecuta en la fase normal del compilador y tiene acceso a toda la información que puede tener el compilador de Kotlin. Esto le permite interceptar y transformar el IR (representación intermedia) de todas nuestras funciones componibles para agregar información adicional.

Entre ellos, una cosa que el compilador de ComposeComposable agregará a cada función es agregar un Composerparámetro al final de la lista de parámetros. Este parámetro es implícito, lo que significa que el desarrollador no lo sabrá al escribir el código. Su instancia se inyecta en tiempo de ejecución y se reenvía a todas las Composablellamadas secundarias, por lo que se puede acceder desde todos los niveles del árbol.

inserte la descripción de la imagen aquí

Supongamos que tenemos el siguiente código:

inserte la descripción de la imagen aquí

Luego, el compilador de Compose lo traducirá a lo siguiente:

inserte la descripción de la imagen aquí

Podemos ver que Composerse reenvía a todas las llamadas del cuerpo Composable. Sobre esta base, el compilador de Compose impone reglas estrictas a las funciones componibles: solo se pueden llamar desde otras funciones componibles . Dado que esto realmente requiere un contexto de llamada, garantiza que el árbol solo esté compuesto de funciones componibles para que puedan Composerreenviarse.

ComposerCompose runtimees el puente entre el código componible que escribimos como desarrolladores y el Las funciones componibles usarán esto para emitir operaciones de cambio en el árbol, notificando así Compose runtimela forma del árbol para construir o actualizar su estado de representación en memoria.

idempotente

Las funciones componibles son idempotentes con respecto a los árboles de nodos que generan . Es decir: volver a ejecutar una función Composable varias veces con los mismos parámetros de entrada debería dar como resultado el mismo árbol . El tiempo de ejecución de Jetpack Compose se basa en esta suposición para cosas como la recomposición.

En Jetpack Compose, la recomposición es el acto de volver a ejecutar funciones componibles cuando sus entradas cambian, para que puedan emitir información actualizada y actualizar el árbol. Compose runtimeDebe ser posible recombinar funciones componibles en cualquier momento y por varias razones.

El proceso de reorganización atravesará todo el árbol, verificando qué nodos necesitan ser reagrupados (repetidamente). Solo se reagruparán los nodos con cambios de entrada, mientras que el resto se omitirá . Omitir un nodo solo es posible si la función Composable que lo representa es idempotente , porque el tiempo de ejecución puede suponer que dada la misma entrada, producirá el mismo resultado. Estos resultados ya están en la memoria, por lo que Compose no necesita volver a ejecutarlo.

deshacerse de los efectos secundarios incontrolables

Un efecto secundario es escapar del control de la función que lo llamó para hacer algo inesperado. La lectura de datos de un caché local, la realización de una solicitud de red o la configuración de una variable global pueden considerarse efectos secundarios. Hacen que la invocación de una función dependa de factores externos que pueden afectar su comportamiento: estado externo en el que se puede escribir desde otros subprocesos, API de terceros que pueden generar excepciones, etc. En otras palabras, en este punto la función no depende únicamente de su entrada para producir un resultado.

Los efectos secundarios hacen que las funciones tengan fuentes de entrada ambiguas o no deterministas . Esto es malo para Compose porque el tiempo de ejecución espera que las funciones componibles sean predecibles (es decir, deterministas) para que puedan volver a ejecutarse de forma segura varias veces.

Si una función componible ejecuta efectos secundarios, puede producir un estado de programa diferente cada vez que se ejecuta, lo que la convierte en no idempotente .

Supongamos que hacemos una solicitud de red directamente desde el cuerpo de una función Composable, como esta:

inserte la descripción de la imagen aquí
Esto sería muy peligroso, ya que la función podría volver a Compose runtimeejecutarse muchas veces en un corto período de tiempo, lo que provocaría que las solicitudes de red se activaran varias veces y se salieran de control. La realidad es peor que eso, porque estas ejecuciones pueden ocurrir en diferentes subprocesos sin ninguna coordinación.

Compose runtimeSe reserva el derecho a elegir una estrategia de ejecución para funciones componibles. Puede programar reorganizaciones en diferentes subprocesos para aprovechar múltiples núcleos para aumentar el rendimiento, o puede ejecutar funciones componibles en cualquier orden de acuerdo con sus necesidades o prioridades (por ejemplo: las combinaciones que no se muestran en la pantalla se pueden asignar a una prioridad más baja)

Otra advertencia común de efectos secundarios es que podemos hacer que una función Composable dependa del resultado de otra función Composable, imponiendo una relación de orden. Queremos evitar esto a toda costa. Por ejemplo:

inserte la descripción de la imagen aquí
En este fragmento de código, Header, ProfileDetaily EventListse pueden ejecutar en cualquier orden, incluso en paralelo.
No debemos escribir lógica que asuma un orden particular de ejecución, ProfileDetailcomo la lectura Headerde variables externas que se espera que se escriban.

En general, los efectos secundarios no son ideales en las funciones componibles. Debemos tratar de hacer que todas las funciones de Composable no tengan estado , de modo que tomen todas las entradas como parámetros y solo las usen para producir resultados. Esto hace que Composables sea más simple, más confiable y altamente reutilizable. Sin embargo, los efectos secundarios son necesarios cuando se escriben programas con estado que necesitan ejecutar solicitudes de red, guardar información en bases de datos, usar cachés en memoria, etc. Entonces, en algún momento, debemos ejecutarlos (generalmente en la raíz del árbol componible). Por este motivo, Jetpack Compose proporciona un mecanismo para llamar de forma segura a las operaciones de efectos secundarios desde las funciones de Composable en un entorno controlado: la API de efectos secundarios .

La API de efectos secundarios hace que las operaciones de efectos secundarios sean conscientes del ciclo de vida de Composable, por lo que pueden vincularse/controlarse por él. Permiten que las operaciones de efectos secundarios se cancelen automáticamente cuando Composable se descarga del árbol, se vuelven a activar cuando cambian las entradas de efectos secundarios e incluso mantienen el mismo efecto secundario (solo llamado una vez) en múltiples recomposiciones. Nos permitirán evitar invocar operaciones de efectos secundarios directamente desde el cuerpo del Composable sin ningún control. Cubriremos los controladores de efectos secundarios en detalle en capítulos posteriores.

reiniciable

Hemos mencionado algunas veces que las funciones componibles se pueden recomponer, por lo que no son como las funciones estándar tradicionales en el sentido de que no se llaman solo una vez como parte de la pila de llamadas.

Así es como se ve una pila de llamadas normal. Cada función se llama una vez y puede llamar a una o más funciones.

inserte la descripción de la imagen aquí
Por otro lado, dado que las funciones componibles se pueden reiniciar (reejecutar, recomponer) varias veces, el tiempo de ejecución mantiene referencias a ellas . Así es como se ve un árbol de llamadas componible:

inserte la descripción de la imagen aquí

where Composable 4y Composable 5se vuelven a ejecutar después de que cambie la entrada.

Compose elige qué nodos del árbol reiniciar para mantener su representación en memoria siempre actualizada. Las funciones componibles están diseñadas para ser reactivas y capaces de volver a ejecutarse en función de los cambios de estado que observan.

El compilador de Compose encuentra todas las funciones Composable que leen algún estado y genera el código necesario para decirle al tiempo de ejecución que es hora de reiniciarlas. Las funciones componibles que no leen el estado no necesitan reiniciarse, por lo que no es necesario decirle al tiempo de ejecución cómo hacerlo.

ejecución rápida

Podemos pensar en las funciones componibles y los árboles de funciones componibles como una forma rápida, declarativa y liviana de crear una descripción de un programa que se mantendrá en la memoria y se interpretará/materializará en una etapa posterior.

Las funciones componibles no compilan ni devuelven UI . Simplemente emiten datos para construir o actualizar estructuras en memoria. Esto los hace muy rápidos y permite que el tiempo de ejecución los ejecute varias veces sin temor. A veces sucede con mucha frecuencia, como en cada fotograma de una animación.

Los desarrolladores deben ser conscientes de esto al escribir código y tratar de cumplir con esta expectativa tanto como sea posible. Cualquier operación computacional que pueda causar un alto costo de tiempo debe ejecutarse en una corrutina y siempre envolverla en una API de efectos secundarios que tenga en cuenta el ciclo de vida.

memoria de ubicación

La memoria del lugar es una forma de memoria de la función . La memorización de funciones es la capacidad de una función de almacenar en caché sus resultados en función de sus entradas para que no sea necesario volver a calcular cada vez que se llama para las mismas entradas. Como se mencionó anteriormente, esto solo es posible con funciones puras (deterministas), porque podemos estar seguros de que siempre devolverán el mismo resultado para la misma entrada, por lo que podemos almacenar en caché y reutilizar el valor.

La memorización de funciones es una técnica bien conocida en el paradigma de la programación funcional, donde los programas se definen como composiciones de funciones puras.

En la memoria de funciones , una llamada de función se puede identificar mediante una combinación de su nombre , tipo y valores de parámetros . Y estos elementos se pueden usar para crear una clave única para almacenar/indexar/leer resultados almacenados en caché en llamadas posteriores. Pero en Compose, se considera un elemento adicional: las funciones componibles tienen un conocimiento invariable de su posición en el código fuente . Cuando se llama a la misma función con los mismos valores de parámetro pero en diferentes ubicaciones, el tiempo de ejecución generará diferentesid (únicos en la función principal):

inserte la descripción de la imagen aquí
El árbol en memoria almacenará tres instancias diferentes, cada una con una identificación diferente.

inserte la descripción de la imagen aquí

La identidad del Composable se conserva durante la recomposición, por lo que el tiempo de ejecución puede usar esta identidad para determinar si se llamó antes a un Composable y omitirlo si es posible.

A veces, Compose runtimeasignar un identificador único es difícil para un usuario. Un ejemplo simple es generar una lista de Composables a partir de un bucle:

inserte la descripción de la imagen aquí

En este caso, se llama desde el mismo lugarTalk(talk) cada vez , pero cada uno Talkrepresenta un elemento diferente en la lista y, por lo tanto, un nodo diferente en el árbol. En este caso, Compose runtimeconfíe en el orden de las llamadas para generar llamadas únicas idy aún poder distinguirlas.

Al agregar un nuevo elemento al final de la lista, este código aún funciona bien porque el resto de las llamadas permanecen en el mismo lugar que antes. Pero, ¿y si añadimos elementos en la parte superior o en algún punto intermedio? Compose runtimereagrupará todo debajo de esa posición Talka medida que cambiaron de posición, incluso si su entrada no cambió. Esto es muy ineficiente (especialmente para listas largas), ya que estas llamadas deberían haberse saltado.

Para resolver este problema, Compose proporciona un keyComposable para la configuración, por lo que podemos asignar manualmente uno explícito a la llamada Composable key:

inserte la descripción de la imagen aquí
En este ejemplo, usamos talk.id(posiblemente único) como cada uno Talk, keylo que permitirá que el tiempo de ejecución mantenga la identidad de todos los elementos de la lista, independientemente de su posición.

La memorización permite que el tiempo de ejecución recuerde funciones componibles por diseño. Cualquier función componible que el compilador de Compose deduzca que puede reiniciarse también debe poder omitirse y, por lo tanto, recordarse automáticamente . Compose se basa en este mecanismo.

A veces, los desarrolladores necesitan usar esta estructura de memoria de una manera más detallada que el alcance de las funciones componibles. Supongamos que queremos almacenar en caché los resultados de cálculos pesados ​​que ocurren en las funciones componibles. El tiempo de ejecución de Compose proporciona rememberfunciones para esto:

inserte la descripción de la imagen aquí
Aquí usamos rememberel resultado de la operación en caché para precalcular el filtro de la imagen. La clave del valor del caché de índice se basará en la posición de la llamada en el código fuente y la entrada de la función (en este caso, la ruta del archivo). rememberUna función es solo una función componible que sabe cómo leer y escribir en la estructura de memoria que contiene el estado del árbol. Sólo expone este mecanismo de " memoria de ubicación " a los desarrolladores .

En Compose, la memoria no está en el nivel de la aplicación. Cuando se memoriza algo, se hace en el contexto del Composable que lo llamó . En el ejemplo anterior, es FilteredImage. En la práctica, Compose buscará el valor en caché del rango de ranuras en la estructura de memoria que almacena la información componible. Esto lo hace más como un singleton en este ámbito . Si se llama al mismo Composable desde una clase principal diferente, se devuelve una nueva instancia de ese valor .

Similitudes con las funciones de suspensión

Las funciones de suspensión de Kotlin solo se pueden llamar desde otras funciones de suspensión, por lo que también necesitan un contexto de llamada. Esto garantiza que las funciones de suspensión solo se puedan encadenar y le da al compilador de Kotlin la oportunidad de inyectar y reenviar el entorno de tiempo de ejecución en todos los niveles de computación. Esto agrega un parámetro adicional al final de la lista de argumentos de cada función de suspensión: Continuation. Este parámetro también está implícito, por lo que los desarrolladores no necesitan saberlo. Las continuaciones se pueden usar para desbloquear algunas funciones nuevas y potentes en el idioma.

Es bastante similar a lo que hace el compilador Compose antes mencionado, ¿no es así?

Las continuaciones son similares a las devoluciones de llamada en el sistema de rutinas de Kotlin. Le dice al programa cómo proceder.

Por ejemplo, el siguiente código:

inserte la descripción de la imagen aquí

Será reemplazado por el compilador Kotlin con:

inserte la descripción de la imagen aquí

ContinuationContiene toda la información que necesita el tiempo de ejecución de Kotlin para suspender y reanudar la ejecución desde diferentes puntos de suspensión del programa. Esto hace que colgar sea otro buen ejemplo de cómo se puede usar un contexto de llamada como un medio para llevar información implícita a través del árbol de ejecución. Información que se puede usar en tiempo de ejecución para habilitar funciones de idioma avanzadas.

Del mismo modo, también podemos @Composableentenderlo como una característica del lenguaje. Hace que las funciones estándar de Kotlin sean reiniciables, reactivas, etc.

En este punto, una pregunta justa es por qué el equipo de Jetpack Compose no lo utilizó suspendpara lograr el comportamiento que deseaba. Bueno, aunque estas dos funciones son muy similares en los patrones que implementan, ambas permiten funciones completamente diferentes en el lenguaje.

La interfaz de Continuación es muy específica en cuanto a suspender y reanudar la ejecución, por lo que se modela como una interfaz de devolución de llamada, para lo cual Kotlin genera una implementación por defecto que contiene lo necesario para ejecutar saltos, coordinar diferentes puntos de suspensión, compartir datos entre ellos, etc. .todos los mecanismos. El caso de uso de Compose es muy diferente, ya que su objetivo es crear una representación en memoria de un gráfico de llamadas grande que se puede optimizar de manera diferente en tiempo de ejecución.

Una vez que entendemos las similitudes entre las funciones componibles y las funciones de suspensión, es interesante considerar la idea de "colorear funciones".

Colores para funciones componibles

Las funciones componibles tienen diferentes restricciones y capacidades que las funciones estándar. Vienen en diferentes tipos (más sobre eso más adelante) y modelan preocupaciones muy específicas. Esta distinción puede entenderse como una forma de colorear funciones , ya que de alguna manera representan una clase separada de funciones .

La coloración de funciones fue presentada por Bob Nystrom del equipo Dart de Google en una publicación de blog titulada ¿ De qué color son sus funciones? Él explica por qué async y sync no funcionan bien juntos, porque no puede llamar a una función asíncrona desde una función de sincronización a menos que también haga la sincronización asíncrona, o proporcione un mecanismo de espera que permita llamar a una función asíncrona y esperar sus resultados. Por eso Promisey async/awaites introducido por algunas bibliotecas y lenguajes. Este es un intento de recuperar la componibilidad. Bob Nystrom se refiere a estas dos categorías de funciones como dos "colores de función" diferentes.

En Kotlin, suspendpretende resolver el mismo problema. Sin embargo, las funciones de suspensión también están coloreadas porque solo podemos llamar funciones de suspensión desde dentro de otras funciones de suspensión. Componer programas utilizando funciones estándar y funciones de suspensión requiere algunos mecanismos de integración especiales ( puntos de lanzamiento de corrutina ). La integración es opaca para los desarrolladores.

En general, esta limitación es de esperar. En realidad, estamos modelando dos clases de funciones, que representan conceptos de naturaleza completamente diferente. Es como si estuviéramos hablando de dos idiomas diferentes. Tenemos dos operaciones: una operación síncrona diseñada para calcular un resultado que se devuelve inmediatamente y una operación asíncrona que se desarrolla con el tiempo y finalmente proporciona un resultado (que puede tardar más en completarse).

En Jetpack Compose, la situación de las funciones componibles es equivalente. No podemos llamar de forma transparente funciones componibles desde funciones estándar. Si queremos hacer esto, necesitamos un punto de integración (por ejemplo: Composition.setContent). Las funciones componibles tienen un objetivo completamente diferente al de las funciones estándar. No se utilizan para escribir la lógica del programa , sino para describir cambios en el árbol de nodos .

Esto puede parecer ridículo. Sabemos que una de las cosas buenas de las funciones componibles es que puedes usar la lógica para declarar la interfaz de usuario. Esto significa que a veces necesitamos llamar funciones componibles desde funciones estándar. Por ejemplo:

inserte la descripción de la imagen aquí
Aquí SpeakerComposable se forEachllama desde la función lambda de , pero el compilador no parece informar un error. ¿Cómo funciona esta forma de mezclar diferentes colores funcionales?

La razón es que forEachla función está inlineen línea. Los operadores de conjuntos se declaran inlinede tal manera que alinean las lambdas en la persona que llama y las hacen válidas como si no hubiera espacios adicionales. En el ejemplo anterior, Speakerla llamada a Composable está integrada SpeakerList, lo cual está permitido ya que ambas son Composablefunciones. Al aprovechar la función en línea, podemos evitar el problema de la coloración de funciones para escribir lógica combinada. Eventualmente, nuestros árboles también consistirán solo en funciones componibles.

Pero, ¿el problema de coloración de funciones es realmente un problema?

Bueno, tal vez esto sería así, si necesitáramos combinar los dos tipos de funciones y seguir saltando de una a la otra. Sin embargo, ninguno de ellos es el caso de suspendo . @ComposableAmbos mecanismos requieren un punto de integración, por lo que obtenemos una pila de llamadas completamente coloreada (con cualquier suspendfunción o Composablefunciones) más allá de ese punto. En realidad, esto es una ventaja, ya que permite que el compilador y el tiempo de ejecución traten las funciones coloreadas de manera diferente, y habilita algunas funciones de lenguaje más avanzadas que no son posibles con las funciones estándar.

En Kotlin, suspendse permite modelar programas asíncronos sin bloqueo de una manera muy idiomática y expresiva. El lenguaje es capaz de expresar conceptos muy complejos de una forma extremadamente sencilla: añadiendo suspendmodificadores a las funciones. Por otro lado, @Composablehace que las funciones estándar se puedan reiniciar, omitir y responder, que no están disponibles en las funciones estándar de Kotlin.

Tipos de funciones componibles

@ComposableLas anotaciones cambian efectivamente el tipo de una función en tiempo de compilación. Desde un punto de vista sintáctico, el tipo de una función Composable es @Composable (T) -> A, donde Apuede ser Unit, o cualquier otro tipo (si la función devuelve un valor, como rememberuna función). Los desarrolladores pueden usar este tipo para declarar lambdas componibles, como cualquier lambda estándar en Kotlin.

inserte la descripción de la imagen aquí

Las funciones componibles también pueden tener @Composable Scope.() -> Atipos, que generalmente solo se usan para limitar la información a un objeto componible en particular. Por ejemplo:

inserte la descripción de la imagen aquí
Desde la perspectiva del lenguaje, los tipos existen para proporcionar información al compilador con el fin de realizar una verificación estática rápida, a veces generar algún código útil y demarcar/refinar cómo se usan los datos en el tiempo de ejecución. @ComposableLas anotaciones cambian la forma en que se validan y utilizan las funciones en tiempo de ejecución, por lo que se considera que tienen un tipo diferente al de las funciones normales.

Resumir

  • El significado de la función Composable es enviar un nodo LayoutNode a la combinación Composición e insertarlo en el árbol de composición durante la ejecución.

  • La anotación @Composable en realidad cambia el tipo de función o el tipo de expresión, y el tiempo de ejecución de Compose realiza optimizaciones de tiempo de ejecución basadas en esto, como composición paralela, reorganización inteligente y memoria de ubicación.

  • El compilador Compose agregará un parámetro Composer al final de la lista de parámetros de cada función Composable, que se implementa modificando el IR en la etapa del compilador y no es visible para los desarrolladores. Las instancias de Composer se inyectan en tiempo de ejecución y se reenvían a todos los Composables secundarios, accesibles en todo el árbol.

  • Componer regla de restricción del compilador: las funciones componibles solo se pueden llamar desde otras funciones componibles. El motivo es precisamente que los parámetros añadidos de Composer se reenvían hacia abajo.

  • Las funciones componibles deben evitar realizar operaciones de efectos secundarios directamente, lo que hace que la entrada no sea determinista; de lo contrario, el tiempo de ejecución no puede garantizar que pueda ejecutar de manera segura múltiples recombinaciones. Las operaciones de efectos secundarios se deben realizar mediante la API de efectos secundarios proporcionada por Compose. La API de efectos secundarios hace que las operaciones de efectos secundarios sean conscientes del ciclo de vida de Composable. Se pueden iniciar cuando se monta Composable desde el árbol y se cancelan automáticamente cuando se desmonta.

  • Las funciones componibles no tienen orden entre sí. No se ejecutarán en el orden en que se escribe el código, pero se pueden ejecutar en cualquier orden o simultáneamente, lo que determina el tiempo de ejecución. Por lo tanto, no puede confiar en su orden para escribir la lógica del código.

  • El tiempo de ejecución de Compose contiene una referencia a la función Composable, por lo que la función Composable se puede reiniciar, es decir, se puede volver a ejecutar varias veces, lo que es diferente de la pila de llamadas de función tradicional.

  • Memoria de ubicación: el tiempo de ejecución de Compose generará una identificación (clave) única para cada función de Composable, que contiene la información de ubicación de Composable en el código fuente, es decir, las mismas identificaciones de función con el mismo valor de parámetro llamado en diferentes ubicaciones son diferentes. Podemos asignar una clave explícita a Composable llamando manualmente a key() { }.

  • Las funciones componibles son sorprendentemente similares a las funciones de suspensión de Kotlin. Por ejemplo, las funciones de suspensión solo se pueden llamar en otras funciones de suspensión. El compilador de Kotlin inyectará parámetros de continuación adicionales en las funciones de suspensión.

  • Coloreado de funciones: para las funciones de suspensión de Kotlin, se trata de la distinción entre funciones asíncronas y funciones síncronas. Las funciones ordinarias y las funciones de suspensión representan dos colores de función diferentes. Para funciones componibles, se trata de la distinción entre funciones estándar y componibles.

  • Podemos llamar a otras funciones componibles en algunas API de operaciones de recopilación dentro de las funciones componibles, lo que no viola la regla de "Las funciones componibles solo se pueden llamar desde otras funciones componibles", porque estas API de operaciones de recopilación están en línea en línea, es decir, cualquier función en línea llamar dentro de una función Composable puede llamar directamente a otras funciones Composable.

  • El propósito de la función de suspensión es suspender y reanudar, para resolver la combinación de funciones asíncronas y síncronas, y el propósito de la función Composable es resolver los problemas reiniciables, saltables y receptivos. Es construir o actualizar la representación de la memoria. estado del árbol. Ambos mecanismos requieren un punto de integración en su implementación.

Supongo que te gusta

Origin blog.csdn.net/lyabc123456/article/details/129116380#comments_27492523
Recomendado
Clasificación