How to access all the Objective-C APIs using JSI - Jamie Birch | React Native EU 2022

How to access all the Objective-C APIs using JSI - Jamie Birch | React Native EU 2022

Cómo acceder a todas las APIs de Objective C usando JSI

Introducción y contexto

  • Jamie Burch presenta el tema, mencionando que ha llegado en medio de un tifón para ofrecer una metáfora sobre puentes, anticipando que será un contenido valioso.
  • Se describe la agenda: introducción personal, antecedentes del problema con el acceso a APIs nativas, uso de GSI y acceso al runtime de Objective-C.

Problemas con el acceso a APIs nativas

  • Burch comparte su experiencia trabajando en múltiples plataformas y su frustración con el código nativo, señalando que acceder a APIs nativas puede ser complicado y doloroso.
  • Se menciona que este proceso puede interrumpir el flujo de trabajo y requiere reconstrucciones en Xcode, lo cual es tedioso.

Propuesta de solución

  • La idea central es permitir llamadas directas a todas las APIs nativas desde JavaScript, facilitando la interacción sin necesidad de escribir módulos nativos.
  • Si se logra esto, los desarrolladores no tendrían que lidiar con Xcode ni crear módulos nativos; solo necesitarían conocer los SDK disponibles.

Implementación del módulo JSI

  • Para implementar esta solución se necesita algo rápido y sincrónico que soporte tipos de datos nativos. Esto contrasta con el clásico puente JSON.
  • Se inicia un proyecto estándar de React Native desde una plantilla TypeScript y se configura el entorno en Xcode.

Creación del módulo JSI

  • Burch explica cómo agregar archivos al proyecto Xcode para configurar el módulo JSI.
  • Se detalla la creación e implementación del módulo utilizando RCT Bridge Module como base.

Configuración del ciclo de vida del módulo

  • Durante la instalación del ciclo de vida se obtiene una referencia al puente (bridge), lo cual es crucial para limpiar más tarde.
  • Se importan encabezados JSI y se utiliza un espacio de nombres para simplificar las importaciones necesarias.

Interacción con JavaScript

  • El objetivo es establecer un objeto en el contexto JavaScript. Se crea una cadena JSI desde una cadena C utilizando métodos proporcionados por la biblioteca JSI.

¿Cómo gestionar propiedades en JSI?

Limpieza y gestión de propiedades

  • Se menciona la importancia de limpiar las propiedades establecidas en el objeto global al realizar un refresco completo, utilizando un puntero de tiempo de ejecución para referenciar el objeto.
  • Al limpiar, se debe solicitar un valor indefinido ya que no se puede eliminar directamente una propiedad, pero sí se puede sobrescribir.
  • Se introduce la constante Objective C, que inicialmente será del tipo any, aunque técnicamente es un tipo string.
  • Se establece un efecto secundario (useEffect) que se ejecutará al montar y desmontar el componente, registrando el valor de Objective C en la consola.
  • Se advierte sobre una posible condición de carrera al iniciar, donde el módulo JSI podría no estar instalado correctamente a tiempo.

Manejo de módulos y carga

  • Para resolver problemas con la carga del módulo JSI, se sugiere buscar "RCT export blocking synchronous method" en los módulos de React Native Quick Crypto para implementar una carga explícita del módulo.
  • Se presenta una hoja de trucos sobre cómo manejar diferentes tipos de datos en JSI: cadenas, números y booleanos.

Tipos básicos en JSI

Cadenas

  • Las cadenas son manejadas mediante clases como JSI::String, donde métodos como createFromASCII y createFromUTF8 permiten crear cadenas desde diferentes formatos.

Números

  • La clase JSI::Value permite interpretar números pasados como dobles; también se pueden extraer valores dobles desde objetos NSNumber.

Booleanos

  • En Objective C, los booleanos pueden escribirse como "true" o "yes", pero solo ciertas representaciones funcionan correctamente.

Objetos host y funciones

Creación de funciones en JSI

  • Para crear instancias de objetos host, se utiliza un puntero compartido. Este proceso es complejo pero esencial para la gestión adecuada de memoria.
  • La creación de funciones dentro del contexto JSI permite sumar dos números; esto implica definir argumentos necesarios dentro del cierre (closure).

Creación de Funciones JSI en Objective C

Introducción a los Argumentos y Tipos

  • Se discute cómo manejar una lista de argumentos en JSI, donde cada argumento es un valor JSI. Es importante especificar el tipo deseado para cada uno.
  • Se menciona la creación de una función JSI a partir de una función host, utilizando un puntero de tiempo de ejecución y asignando un nombre a la propiedad.

Utilidades y Eficiencia en JSI

  • Se introduce el archivo JSI utils, que facilita la conversión entre cadenas NS y cadenas JSI, evitando la necesidad de reescribir código repetitivo.
  • La charla se centra en construir proyecciones del tiempo de ejecución Objective C, enfatizando más sobre las posibilidades que sobre la implementación directa.

Objetivos del Proyecto

  • El objetivo es crear un objeto host accesible globalmente desde JavaScript como si fuera Objective C. Este objeto inicialmente parecerá un objeto JavaScript regular.
  • Una vez completado, permitirá acceder a constantes, clases y llamadas a métodos desde JavaScript, manejando tipos de datos nativos adecuadamente.

Implementación Inicial

  • Se planea implementar solo getters por ahora; los setters son similares pero no se cubrirán debido al tiempo limitado.
  • Se comienza creando archivos para objetos host en Objective C que estarán vacíos inicialmente.

Detalles Técnicos sobre Métodos

  • En la interfaz creada se importan encabezados GSI y se define una clase host object Objective C que extiende JSI host object.
  • El método get devolverá el valor para cualquier propiedad solicitada; por ejemplo, al llamar a global.com tostring.

Definición de Propiedades

  • El método get property names definirá las claves enumerables del objeto host. Inicialmente retornará un arreglo vacío.
  • Se explica cómo listar propiedades usando object.keys, lo cual será útil para interactuar con el objeto desde JavaScript.

Exposición del Objeto Host

  • A continuación, se instanciará el objeto host y se envolverá en un puntero compartido para su uso posterior.
  • Al intentar registrar el objeto creado, inicialmente aparecerá como un objeto vacío sin métodos implementados como toString.

API Serializable desde Objective C

Integración de Objective C y JavaScript

Acceso a Variables Globales en JavaScript

  • Se busca hacer disponible una variable global en el lado de JavaScript al acceder a Objective C, específicamente utilizando NSString para retornar el valor de la variable como un string.

Modificación del Getter en el Objeto Host

  • Se planea añadir modificaciones al getter en el objeto host de Objective C, asegurándose de importar la biblioteca Foundation para poder llamar funciones necesarias.

Proceso de Transformación y Registro

  • Al acceder a las claves enumerables, se observa que cada clave especificada se registra. Es importante tener cuidado con llamadas costosas ya que los registros estándar pueden mostrar información sensible.

Exposición de APIs Arbitrarias de Objective C

  • Se discute cómo exponer APIs arbitrarias de Objective C, permitiendo crear instancias y métodos sobre cualquier clase solicitada, no limitándose solo a NSString.

Mejoras en la Clase del Objeto Host

  • La clase del objeto host se mejorará para manejar cualquier tipo nativo. El constructor ahora tomará un puntero a un objeto nativo y permitirá envolver cualquier tipo de dato nativo.

Enumeración y Tipos Nativos Soportados

  • Se introduce una nueva enumeración que expresa los diferentes tipos de referencias nativas soportadas: clases, instancias y objetos globales. Esto permite una mejor gestión durante la construcción del objeto.

Implementación del Constructor

  • En el constructor se determinará el tipo de referencia nativa e inicializará las propiedades correspondientes. Si es global, buscará todas las clases o variables coincidentes; si es una instancia, buscará métodos o propiedades disponibles.

Manejo de Errores con Try-Catch

  • Se implementa un bloque try-catch para manejar excepciones tanto en Objective C como en C++. Esto permite verificar si un objeto es realmente un NS object antes de proceder con operaciones adicionales.

Metaprogramación y Clases Meta

Implementación de Métodos en C++ y Objective-C

Uso de Objetos NS y Propiedades

  • Se discute la necesidad de manejar objetos NS en un contexto de C++, donde se debe decidir sobre la propiedad del puntero. Se opta por el uso de "Bridge" para evitar cambios en la propiedad del puntero.
  • En el método get, se implementan los getters para clases y protocolos, utilizando nsclassFromString para verificar si una cadena corresponde a una clase válida como UIView.

Manejo de Clases y Protocolos

  • Si no se encuentra una clase, se intenta verificar si es un protocolo usando NSProtocolFromString. Si tampoco hay coincidencias, se busca si es una variable global mediante el enlazador dinámico.
  • Para variables globales, se utiliza DLSim para buscar el nombre del símbolo. Si se encuentra, se crea un objeto host; si no, se retorna indefinido.

Invocación de Métodos y Propiedades

  • Si no somos globales ni encontramos métodos válidos, retornamos indefinido. Para instancias o clases válidas, creamos un selector NS con el nombre proporcionado.
  • La implementación del método invoke es compleja y requiere metaprogramación en Objective-C. Se sugiere revisar el repositorio para más detalles.

Acceso a Propiedades

  • Las clases e instancias tienen diferentes formas de acceder a propiedades. Se utiliza un método auxiliar para convertir objetos a valores JSI.
  • El proceso incluye copiar nombres de propiedades mediante metaprogramación en Objective-C; sin embargo, algunos aspectos quedan sin tiempo suficiente para ser discutidos.

Desafíos y Futuras Mejoras

  • Se menciona que existen problemas al implementar esta funcionalidad debido a accesos erróneos a memoria relacionados con referencias fuertes y débiles.
  • A pesar de las dificultades encontradas durante la implementación, existe potencial teórico en la solución presentada. Se busca revisión por parte de expertos en C++.

Consideraciones Finales

  • Preocupaciones sobre gestión de memoria incluyen posibles fugas o pérdida de referencias nativas. La depuración es complicada debido a la naturaleza dinámica del código.

¿Cómo implementar JSI en Android?

Opciones para la implementación de JSI

  • Se sugiere que sería interesante tener un equivalente de Android para JSI, lo que abriría nuevas posibilidades en el desarrollo.
  • Si no se puede realizar la escritura de memoria con JSI, se plantea delegar a NativeScript como alternativa viable.
  • La idea es utilizar JSI para invocar NativeScript desde React Native, lo que podría mejorar la interoperabilidad entre las plataformas.
  • Otra opción discutida es reemplazar el motor JavaScript actual (GSC o V8) por NativeScript SEO V8 y hacer llamadas directas, optimizando así el rendimiento.
Video description

The JavaScript Interface (JSI) allows us to access native APIs directly from the JS context. Unlike traditional JSON bridge-based native modules, API access is synchronous and even non-serialisable data types can be expressed. However, JSI is largely undocumented, so it may be unclear how to use it. In this talk, we'll learn how to write JSI code to bridge any individual API from the Objective-C runtime to JS and demonstrate how we could even bridge *all* the APIs the Objective-C runtime offers, by walking through the approach used in the shirakaba/react-native-native-runtime GitHub repository. Speaker Bio A cross-platform hobbyist and ex-Smart TV app developer, Jamie enjoys putting JavaScript in places it doesn't belong, usually with a view to building apps for foreign language study. Check out more about React Native EU conference: https://www.react-native.eu/ and follow us on Twitter to stay up to date: https://twitter.com/react_native_eu