Un SDK Swift et une API C#, avec 831 Mo entre les deux
Comptez les types que le SDK iOS de HERE expose via Objective-C et vous obtenez une poignée, quatre au dernier décompte. Comptez ce qu'il expose via Swift et vous obtenez le produit entier : rendu de carte, recherche, calcul d'itinéraires, trafic en temps réel, plusieurs centaines de classes. La moitié iOS d'une plateforme cartographique de premier plan est un artefact exclusivement Swift, et .NET n'a aucun moyen natif de la toucher.
Voici l'histoire de la construction de here-sdk-for-maui, nos bindings .NET MAUI pour HERE SDK 4.x (Explore Edition), sous licence MIT. Notre thèse, que tout ingénieur interop sceptique est invité à contester : les parties difficiles n'ont presque rien à voir avec la syntaxe, et presque tout à voir avec la frontière où l'ARC de Swift serre la main au ramasse-miettes de .NET. Un outil déplace des noms d'un langage à l'autre en un après-midi. La garde de la mémoire native, c'est là que le travail de conception s'est joué.
Android, c'est une matinée. iOS, c'est un autre problème.
Un développeur .NET MAUI qui veut la stack HERE demande plus qu'une simple tuile statique : une carte qu'il peut déplacer avec de vrais gestes, et un calcul d'itinéraires avec des instructions que son équipe opérations sait déjà lire. Sur Android, HERE SDK est livré sous forme d'un AAR, et l'outillage de binding standard de .NET consomme ce format. Le travail tient réellement en une matinée.
Sur iOS, le même développeur bute sur un mur d'une espèce bien précise. Objective Sharpie, l'outil qui génère des bindings C# pour les bibliothèques iOS, lit des en-têtes Objective-C. HERE a cessé de publier une surface Objective-C avec le SDK 4.x. Pour la plupart des classes, les symboles Swift restent invisibles au runtime Objective-C tant qu'un type n'est pas délibérément ré-exposé avec @objc. L'en-tête public du SDK lui-même n'expose que quatre types, et rien de toute l'API que quiconque voudrait lier. Quand nous avons consulté les registres habituels au printemps 2026, nous n'avons trouvé aucun autre binding .NET maintenu pour HERE SDK 4.x. Personne n'allait régler ça à notre place, et c'est ainsi que le projet a commencé.
Un pont @objc vers Swift, puis des bindings pour le pont
Le schéma que le .NET MAUI Community Toolkit recommande pour les bibliothèques Swift compte trois étages. Un NativeBridge Swift vit dans un framework et re-déclare une partie de l'API publique du SDK avec des annotations @objc, ce qui pousse Xcode à émettre un en-tête Objective-C pour ces éléments. Objective Sharpie lit cet en-tête et génère les types de binding C#. Au-dessus des deux se trouvent des wrappers C# écrits à la main, qui offrent aux développeurs .NET ce que le binding brut ne peut pas fournir : des méthodes asynchrones et des événements, plus une vraie histoire de libération (dispose).
Deux routes s'étaient terminées en cul-de-sac juste avant celle-ci. La première consistait à pointer Objective Sharpie directement sur le xcframework. Sharpie génère du C# à partir de ce qu'un en-tête déclare. Cet en-tête ne déclare presque rien d'utile. L'après-midi d'outillage avait produit un binding quasi vide. La seconde, un P/Invoke artisanal contre un shim Swift C minimal que nous aurions écrit et maintenu nous-mêmes. Viable en principe, mais chaque chaîne, chaque optionnel, chaque valeur d'erreur et chaque opération de comptabilité ARC devient un travail manuel. Le pont @objc fait déjà ce marshalling correctement. Nous n'avions aucune envie de ré-implémenter une couche déjà résolue, et de la faire mal en prime.
Là où l'ARC serre la main au ramasse-miettes
Le véritable métier du pont, c'est l'aplatissement. Les optionnels Swift deviennent des valeurs nullables C#. Swift traite une énumération à valeurs associées comme un seul type qui transporte sa charge utile, alors que C# n'a pas d'équivalent : le pont renvoie donc une étiquette à côté de champs ordinaires. Une réponse de recherche que Swift décrit comme « les résultats, ou rien, plus une raison » arrive en C# sous la forme d'un code de statut et d'un paramètre de sortie. C'est représentable. C'est aussi là que se décide l'API du wrapper, parce que chaque choix d'aplatissement fuit dans le C# qu'un développeur tapera un an plus tard.
La mémoire native obéit à une règle dans ce code : chaque objet natif exposé en C# appartient à un wrapper qui implémente IDisposable, et la libération détruit la contrepartie native. Les objets HERE sont lourds à un endroit bien précis. Une vue de carte et ses couches détiennent des handles adossés au GPU, si bien qu'un dispose oublié ne plante pas à l'horaire prévu : il fuit, jusqu'à ce que le mécanisme jetsam d'iOS reprenne la mémoire à votre place, à un moment que vous n'avez pas choisi. Nous avons attrapé nos premières erreurs en faisant tourner l'application de référence d'un onglet à l'autre tout en regardant la mémoire dans Instruments, une boucle low-tech qui a pourtant trouvé de vraies fuites. La libération est aussi ce qui a rendu l'API idiomatique plutôt que simplement fonctionnelle, parce qu'un développeur .NET s'attend à ce que using veuille dire quelque chose.
Un trou d'aplatissement reste assumé au grand jour : le tap-to-pick d'un objet carte renvoie null sur iOS alors que le même appel fonctionne sur Android. Nous le listons dans les limites ci-dessous plutôt que de prétendre qu'un wrapper le dissimule. Un trou dans le NativeBridge est un fait du produit, et les faits ont leur place dans les README.
Ce que 831 Mo deviennent dans une build de release
Le xcframework iOS d'Explore Edition v4.25.5.0 pèse environ 831 Mo, et ce chiffre survit à la passe de stripping que nous avons ajoutée à la build de release. À l'intérieur se trouvent les slices par plateforme et par architecture, dont des builds simulateur qu'aucun appareil en production ne touchera jamais. Le stripping, c'est justement ces slices simulateur retirées du packaging de release. Il ne retire pas le coût au niveau des sources, parce qu'un dépôt et son cache CI paient toujours l'artefact complet, et que chaque invocation de Xcode paye encore. Construire les bindings iOS exige macOS, au passage, puisque Xcode est la seule toolchain qui produit le framework. Nous avons remarqué la facture CI bien avant de remarquer le téléchargement : 831 Mo, c'est plus lourd que la plupart des applications entières, et chaque job qui le décompresse paye un loyer pour ce privilège.
Deux plateformes, une surface C#
La règle de conception qui a façonné toute la bibliothèque : si un développeur doit écrire #if IOS pour s'en servir, le wrapper a échoué. En dessous, Android porte le binding AAR et iOS porte le NativeBridge. Au point d'appel, il y a une seule API, et elle est en C# de bout en bout.
builder.UseHereSdkExplore(new HereSdkOptions
{
AccessKeyId = "YOUR_ACCESS_KEY_ID",
AccessKeySecret = "YOUR_ACCESS_KEY_SECRET"
});
<here:HereMapView x:Name="Map" MapScheme="NormalDay" />
Ce que cette surface achète, d'après le site de documentation :
- Un contrôle
MapViewavec contrôle de caméra et gestes, sur plusieurs schémas de carte. - Recherche par texte, par catégorie et autosuggest, avec détails de lieu.
- Calcul d'itinéraires pour les profils voiture, camion, piéton, vélo, scooter et véhicule électrique, avec instructions de manœuvre et affichage visuel du tracé.
- Flux de trafic et incidents en temps réel, avec une légende de facteur d'embouteillage.
- Position multiplateforme via
ILocationService. - Objets cartographiques : marqueurs, polylignes, polygones, flèches et marqueurs 3D.
Les limites, en public, là où vous pouvez les vérifier
- Aucune cible Windows ni macOS Catalyst. La matrice de support s'arrête à Android (API 24 et plus) et iOS (15.2 et plus).
- Pas de CarPlay ni d'Android Auto dans l'API unifiée. Si tout l'intérêt de votre produit est l'écran du tableau de bord, c'est un bloquant, et aucun wrapper ne le masque.
- Le pick de carte renvoie null sur iOS aujourd'hui, le trou du NativeBridge mentionné plus haut. Sur Android, ça fonctionne. Les fonctionnalités qui exigent de sélectionner des objets carte devraient rester Android-first jusqu'à la fermeture du trou.
- La localisation utilise
Geolocationde MAUI comme source primaire plutôt que la stack de positionnement de HERE, ce qui garde un comportement cohérent avec le reste d'une app MAUI. - Les cercles sont approximés en polygones. Acceptable pour un anneau de rayon autour d'un point, faux pour tout ce qui approche la précision topographique.
- Construire les bindings iOS exige un Mac et une installation de Xcode, plus de la patience face aux 831 Mo décompressés.
Ce que nous ferions autrement
Deux regrets, chacun avec sa leçon. D'abord, nous avons lié ce que l'outillage produisait, puis façonné l'API C# ensuite. Les opinions de Sharpie sur le nommage sont ainsi apparues dans les premiers commits et ont dû être reprises à la main. Le bon ordre : écrire la surface C# qu'un développeur MAUI taperait réellement, puis tailler le pont Swift pour l'épouser. Le pont est la spécification, et nous l'avons appris avec un cycle de publication de retard. Ensuite, le stripping de release est arrivé tard, et le dépôt a passé des mois à payer pour les slices simulateur à chaque build. Mesurez le poids de l'artefact dès le premier jour, pas quand un téléchargement finit par inquiéter quelqu'un.
Compilez depuis les sources, puis relisez les limites deux fois
Une note d'installation avant l'argument final : les paquets n'ont jamais été publiés sur un registre, donc le seul chemin supporté est la construction depuis le dépôt, avec la référence API générée sur le site de documentation. Sur .NET 10, les cibles sont net10.0-android et net10.0-ios. En dessous vit HERE SDK Explore Edition v4.25.5.0. La licence est MIT, et l'application de référence tourne directement depuis le clone.
Si votre produit a besoin du calcul d'itinéraires et du trafic de HERE et que votre équipe écrit en C#, le travail de binding décrit ici existe et est publié. Ce que vous lui devez : un après-midi à lire les limites ci-dessus, et un regard honnête sur l'histoire de libération dans votre propre code. La syntaxe d'interop est un problème d'outillage. La mémoire le long de la frontière, c'est la conception.
Merci d'avoir lu jusqu'ici. Si cela ressemble au problème sur lequel vous butez, parlez-nous-en. Un fondateur lit chaque message.
Engager la conversation