Dart 2.18: Interop con Objective-C y Swift
Interoperabilidad mejorada, networking específico de plataforma, inferencia de tipos mejorada, y una actualización importante sobre nuestro lenguaje de null safety…
Interoperabilidad mejorada, red específica de plataforma, inferencia de tipos mejorada, y una actualización importante sobre nuestro roadmap de null safety
#Dart 2.18 está disponible hoy. Esta versión incluye una vista previa de interoperabilidad con Objective-C y Swift y un nuevo paquete de red para iOS/macOS construido sobre esta interoperabilidad. También contiene inferencia de tipos mejorada para funciones genéricas, mejoras de rendimiento para código async, nuevas características de pub.dev, y limpieza de nuestras herramientas y bibliotecas principales.
Finalmente, tenemos los últimos números de estado de migración a null safety y una actualización importante del roadmap sobre nuestro camino hacia un Dart completamente null safe. ¡Por favor lee hasta el final!
Introducción a la interoperabilidad de Dart con Objective-C y Swift
#Presentamos la interfaz de función externa (FFI) de Dart para llamar API nativas de C en 2020 y la lanzamos en Dart 2.12 en marzo de 2021. Desde esa versión, una gran selección de paquetes ha aprovechado esta característica para integrarse con API nativas de C existentes. Algunos ejemplos incluyen
file_picker, printing,
win32, objectbox,
realm, isar,
tflite_flutter, y dbus.
El equipo de Dart quiere que Dart soporte interoperabilidad con todos los lenguajes principales en las plataformas donde Dart se ejecuta. Dart 2.18 cumple el siguiente hito hacia esa meta. Tu código Dart puede llamar código Objective-C y Swift, como se usa típicamente para las API en las plataformas macOS e iOS. Dart soporta este mecanismo de interoperabilidad en cualquier app, desde una app CLI hasta código backend o una UI de Flutter.
Este nuevo mecanismo utiliza el hecho de que el código Objective-C y Swift puede exponerse como código C basado en bindings de API. La herramienta de generación de wrappers de API de Dart,
ffigen, puede crear estos bindings desde los headers de API. Echemos un vistazo a un ejemplo.
Ejemplo de zona horaria usando Objective-C
#macOS tiene una API para consultar información de zonas horarias expuesta en la clase NSTimeZone. Puedes consultar esta API para la zona horaria y el
offset de zona horaria UTC
que el usuario ha configurado para su dispositivo.
La siguiente app de ejemplo en Objective-C usa esta API de zona horaria para obtener la zona horaria del sistema y el offset GMT:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSTimeZone *timezone = [NSTimeZone systemTimeZone]; // Get current time zone.
NSLog(@"Timezone name: %@", timezone.name);
NSLog(@"Timezone offset GMT: %ld hours", timezone.secondsFromGMT/60/60);
}
return 0;
}
La app importa Foundation.h, que contiene los headers de API para la biblioteca Foundation de Apple. Luego, dentro del método
main, llama al método systemTimeZone de la clase
NSTimeZone. Este método devuelve una instancia de NSTimeZone con la zona horaria seleccionada en el dispositivo. Finalmente, la app muestra dos líneas en la consola con el nombre de la zona horaria y el offset UTC en horas.
Si ejecutas esta app, debería devolver algo parecido a lo siguiente, dependiendo de tu ubicación:
Timezone name: Europe/Copenhagen
Timezone offset GMT: 2 hours
Ejemplo de zona horaria usando Dart
#Repliquemos este resultado con Dart usando la nueva interoperabilidad con Objective-C.
Primero crea una nueva app CLI de Dart:
$ dart create timezones
Luego edita tu archivo pubspec para que contenga la configuración de ffigen. Esta configuración apunta al archivo header y lista qué interfaces de Objective-C deberían generar wrappers:
ffigen:
name: TimeZoneLibrary
language: objc
output: "foundation_bindings.dart"
exclude-all-by-default: true
objc-interfaces:
include:
- "NSTimeZone"
headers:
entry-points:
- "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/
Headers/NSTimeZone.h"
Esto selecciona los bindings de Objective-C para los headers en NSTimeZone.h e incluye solo las API en la interfaz
NSTimeZone. Para generar los wrappers, ejecuta ffigen:
$ dart run ffigen
Este comando crea un nuevo archivo, foundation_bindings.dart, que contiene un conjunto de bindings de API generados. Usando este archivo de bindings, podemos escribir nuestro método
main en Dart. Este método refleja el código Objective-C:
void main(List<String> args) async {
const dylibPath =
'/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation';
final lib = TimeZoneLibrary(DynamicLibrary.open(dylibPath));
final timeZone = NSTimeZone.getLocalTimeZone(lib);
if (timeZone != null) {
print('Timezone name: ${timeZone.name}');
print('Offset from GMT: ${timeZone.secondsFromGMT / 60 / 60} hours');
}
}
¡Eso es todo! Este nuevo soporte está disponible en estado experimental comenzando con Dart 2.18 de hoy. Esto mejora el soporte general de interoperabilidad de Dart para llamar API de macOS e iOS directamente. Esto, a su vez, complementa los plugins de Flutter, con un nuevo soporte que funciona en cualquier app Dart, y que te permite llamar API de macOS e iOS directamente desde código Dart.
Agradecemos tu feedback. Cuéntanos qué funcionó, qué podría cambiarse, o qué problemas experimentaste comentando en el issue de feedback en GitHub. Para más información sobre esta interoperabilidad, consulta la guía de interoperabilidad con Objective-C y Swift.
Bibliotecas http específicas de plataforma
#Dart incluye una biblioteca http general y multiplataforma. Esta biblioteca te permite escribir código sin preocuparte por las especificidades de la plataforma. En ocasiones, podrías querer escribir código específico para las API de red de una plataforma anfitriona particular.
Por ejemplo, la biblioteca de red de Apple NSURLSession
permite especificar red solo por WiFi o que requiere una VPN. Para soportar estos casos de uso, hemos creado un nuevo paquete de red destinado a las plataformas macOS e iOS,
cupertino_http. Este se basa en la nueva interoperabilidad con Objective-C mencionada en la sección anterior. Usa un gran conjunto de wrappers de API
generados
desde las API de red de Apple en Foundation.
Ejemplo de biblioteca http Cupertino
#El siguiente ejemplo configura el cliente http de una app de Flutter para usar la biblioteca cupertino_http en macOS e iOS y la biblioteca http regular de Dart desde
dart:io en otras plataformas:
late Client client;
if (Platform.isIOS || Platform.isMacOS) {
final config = URLSessionConfiguration.ephemeralSessionConfiguration()
..allowsCellularAccess = false
..allowsExpensiveNetworkAccess = false;
client = CupertinoClient.fromSessionConfiguration(config);
} else {
client = Client(); // Uses an HTTP client based on dart:io
}
Después de esta configuración inicial, la app realiza cualquier llamada de red posterior en el cliente específico. Por ejemplo, una petición http
get() ahora se ve así:
final response = await get(
Uri.https(
'www.googleapis.com',
'/books/v1/volumes',
{'q': 'HTTP', 'maxResults': '40', 'printType': 'books'},
),
);
Cuando no puedas usar la interfaz común del cliente, puedes llamar directamente a las API de red de Apple usando la biblioteca
cupertino_http:
final session = URLSession.sessionWithConfiguration(
URLSessionConfiguration.backgroundSession('com.example.bgdownload'),
onFinishedDownloading: (s, t, fileUri) {
actualContent = File.fromUri(fileUri).readAsStringSync();
});
final task = session.downloadTaskWithRequest(
URLRequest.fromUrl(Uri.https(...))
..resume();
Red específica de plataforma en apps multiplataforma
#Al diseñar esta característica, el objetivo seguía siendo mantener las apps lo más multiplataforma posible. Para cumplir este objetivo, mantuvimos nuestro conjunto de API
http general y multiplataforma para operaciones http básicas, y permitimos configurar por plataforma qué biblioteca de red usar. Puedes minimizar la cantidad de código específico de plataforma que necesitas escribir usando la
API Client de package:http. Esta API puede configurarse por plataforma pero usarse de manera independiente de la plataforma.
Dart 2.18 ofrece soporte experimental de dos bibliotecas http específicas de plataforma que soportan la
API Client de package:http:
-
cupertino_httpbasado enNSURLSessionpara macOS/iOS. -
cronet_httpbasado en Cronet, la biblioteca de red popular en Android.
Combinar una API de cliente común con varias implementaciones de HTTP te ofrece lo mejor de ambos mundos. Puedes obtener comportamiento específico de plataforma mientras mantienes tus apps desde un único conjunto de fuentes compartidas para todas tus plataformas. Nos encantaría escuchar tu feedback en este issue de GitHub.
Inferencia de tipos mejorada
#Dart usa muchas funciones genéricas. Considera el método
fold, que reduce una colección de elementos a un solo valor. El siguiente ejemplo calcula la suma de una lista de enteros:
List<int> numbers = [1, 2, 3];
final sum = numbers.fold(0, (x, y) => x + y);
print('The sum of $numbers is $sum');
Con Dart 2.17 o anterior, este método devuelve un error de tipo:
line 2 • The operator '+' can't be unconditionally invoked
because the receiver can be 'null'.
La inferencia de tipos de Dart no podía fluir información entre los argumentos. Esto resultaba en incertidumbre sobre el tipo de
x. Para remediar el posible error, necesitabas especificar el tipo:
final sum = numbers.fold(0, (int x, int y) => x + y);
Dart 2.18 mejora la inferencia de tipos. El ejemplo anterior pasa el análisis estático y puede inferir que tanto x como y son ints no nulos. Este cambio te permite escribir código Dart más conciso manteniendo las propiedades completas de robustez de los tipos fuertemente inferidos.
Mejoras de rendimiento de async
#Esta versión de Dart mejora cómo el Dart VM aplica el método async y las funciones generadoras
async*/sync*. Esto reduce el tamaño del código. En dos grandes apps internas de Google, vimos una reducción del tamaño del snapshot AOT de alrededor del 10%. También vimos un aumento de rendimiento en nuestros microbenchmarks.
Estos cambios incluyen pequeños cambios de comportamiento adicionales; para más información, consulta el changelog.
Mejoras de pub.dev
#En conjunto con la versión 2.18, hemos hecho dos cambios en el repositorio de paquetes pub.dev.
A menudo, individuos mantienen paquetes publicados en pub.dev en su tiempo libre. Esto puede ser costoso, tanto en términos de tiempo como de finanzas. Para facilitar los patrocinios, ahora soportamos una nueva etiqueta de
funding en el pubspec, que puede ser usada por los publicadores de paquetes para listar enlaces a una o más formas de patrocinar el paquete. Estos enlaces se muestran luego en
pub.dev en la barra lateral:
Para más información, consulta la documentación de pubspec.
Además, nos gustaría fomentar un ecosistema rico de paquetes de código abierto. Para destacar esto, la puntuación automatizada de paquetes en
pub.dev otorga 10 puntos adicionales a los paquetes que usan una licencia aprobada por OSI.
Algunos breaking changes
#Dart tiene un fuerte enfoque en la simplicidad y la facilidad de aprendizaje. Constantemente intentamos mantener un equilibrio cuidadoso al añadir nuevas capacidades. Un método para mantener las cosas simples es eliminar funcionalidad y API históricas con poco uso o mejores reemplazos. Dart 2.18 limpia elementos en esta categoría, incluyendo algunos breaking changes menores:
-
Añadimos la herramienta CLI unificada de
dartpara desarrolladores en octubre de 2020. En 2.18, completamos la transición. Esta versión elimina las dos últimas herramientas deprecadas:dart2js(usadart compile js) ydartanalyzer(usadart analyze). -
Con la introducción del versionado del lenguaje,
pubgenera un nuevo archivo de resolución:.dart_tool/package_config.json.El archivo anterior,.packages, usaba un formato que no podía contener versiones. Descontinuamos el uso del archivo.packages. Si tienes archivos.packages, puedes eliminarlos. -
Los mixins de clases que no extienden
Objectno pueden ser usados (breaking change #48167). Este comportamiento nunca fue intencional. -
La propiedad
urideRedirectExceptiondedart:iose ha cambiado a nullable (breaking change #49045). -
Las constantes en las API de red de
dart:ioque seguían la convención SCREAMING_SNAKE han sido eliminadas (breaking change #34218; anteriormente deprecadas). Usa las constantes lowerCamelCase correspondientes en su lugar. -
El Dart VM ya no restaura la configuración inicial de la terminal al salir. Los programas que cambian la configuración de
StdinlineModeyechoModeson ahora responsables de restaurar la configuración al salir del programa (breaking change #45630).
Actualización de null safety
#Estamos muy contentos de ver el amplio uso de null safety desde su versión beta en noviembre de 2020 y la versión de Dart 2.12 en marzo de 2021.
Primero, los desarrolladores de apps de la mayoría de los paquetes más populares en pub.dev migraron a null safety. El análisis muestra que el 100% de los 250 paquetes más usados y el 98% de los 1.000 más usados soportan null safety.
Segundo, la mayoría de los desarrolladores de apps trabajan en bases de código con migración completa a null safety. Esto es crucial. El
sound null safety completo de Dart no se activa hasta que migras todo el código y todas las dependencias (incluyendo las transitivas). Rastreamos esto vía telemetría desde los comandos de
flutter run.
El siguiente gráfico muestra las ejecuciones de null safety no estricto vs. sound null safety de flutter run. Antes de la introducción de null safety, no había ninguna de los dos. Siguió un crecimiento rápido de null safety no estricto. A medida que las apps empezaron a migrar a null safety, los desarrolladores hicieron una migración parcial. Algunas partes todavía necesitaban migrarse. Con el tiempo, vemos un crecimiento muy saludable de sesiones de sound null safety. Para finales del mes pasado, había cuatro veces más sesiones de sound null safety en comparación con las no estrictas. ¡Esperamos que, en los próximos trimestres, veamos el sound null safety acercarse al 100%!
Una actualización importante del roadmap de null safety
#Soportar tanto null safety no estricto como sound null safety añade sobrecarga y complejidad.
Primero, los desarrolladores de Dart necesitan aprender y entender ambos modos. Al leer un fragmento de código Dart, verifica la versión del lenguaje para ver si los tipos son no nulos por defecto (Dart 2.12 y posterior) o nulos por defecto (Dart 2.11 y anterior).
En segundo lugar, soportar ambos modos en nuestros compiladores y runtimes ralentiza la evolución del SDK de Dart para soportar nuevas características.
Basados en la sobrecarga del null safety no estricto y los números de adopción muy positivos mencionados en la sección anterior, nuestro objetivo es transicionar a soportar únicamente sound null safety y descontinuar los modos non-null safety y null safety no estricto. Hemos programado provisionalmente esto para su lanzamiento a mediados de 2023.
Esto significaría descontinuar el soporte para Dart 2.11 y anteriores. Los archivos pubspec con una restricción de SDK cuyo límite inferior sea menor a 2.12 ya no se resolverían en Dart 3 y posteriores. En código fuente que contenga marcadores de lenguaje, estos fallarían si están configurados a menos de 2.12 (como// @dart=2.9).
Si ya migraste a sound null safety, tu código funcionará con null safety completo en Dart 3. Si no lo has hecho, ¡por favor migra ahora! Para más información sobre estos cambios, consulta este issue de GitHub.
Resumen
#El nuevo soporte para interoperabilidad, red, inferencia de tipos, y pub.dev está disponible hoy. Para comenzar, puedes descargar directamente la
versión de Dart 2.18, o obtenerla integrada como parte de la versión del SDK de Flutter 3.3
de hoy.