← Tous les articles

Source unique de vérité : SwiftData, MCP, iCloud

Get Bananas compte trois appelants capables d’écrire dans la même liste de courses. Un humain qui coche des lignes dans l’app iOS. Apple Intelligence qui achemine une requête Siri via un AppIntent. Une session Claude Code qui appelle un outil MCP par stdio. Dans la tête de l’utilisateur, la liste est une chose unique et canonique ; toute la question est de savoir où elle réside côté stockage, et quel appelant l’emporte en cas de désaccord.

L’article de synthèse a nommé les trois surfaces d’une app iOS : humaine, Apple Intelligence, agent. Chaque surface doit lire et écrire le même état métier. C’est cette exigence qui produit l’erreur d’architecture livrée dans trop d’apps : chaque surface reçoit son propre magasin de données, les surfaces divergent, et l’utilisateur découvre trois versions différentes de sa liste selon celle qu’il a touchée en dernier. L’approche qui tient, c’est une source unique de vérité assortie de chemins de synchronisation explicites entre les surfaces et le substrat.

Cet article nomme les substrats possibles, les règles de résolution de conflits que chacun impose, et l’architecture qui tient quand les trois classes d’appelants peuvent écrire. La démonstration s’appuie sur l’agencement réel de Get Bananas : SwiftData pour l’état interne à l’app, un fichier JSON sur iCloud Drive pour la synchronisation inter-processus, un serveur MCP qui lit et écrit ce même fichier depuis l’extérieur du bac à sable iOS.1

En bref

  • Trois substrats se composent : SwiftData (en processus, rapide, typé par le schéma), iCloud Drive (inter-processus, orienté fichier, synchronisable), NSUbiquitousKeyValueStore (multi-appareils, clé-valeur, petites charges utiles uniquement).
  • Choisissez quel substrat fait autorité pour chaque domaine. La résolution de conflits découle mécaniquement de ce choix ; ce n’est pas un problème distinct.
  • Les trois surfaces (humaine, Apple Intelligence, agent) interagissent toutes avec le substrat via la couche métier. C’est là que réside la politique de résolution de conflits.
  • Un horodatage lastModified sur chaque entité mutable constitue la primitive de résolution de conflits la moins coûteuse ; « le dernier qui écrit gagne » en est la politique la moins coûteuse. Les apps qui exigent des garanties plus fortes les paient en logique de fusion explicite.
  • Un serveur MCP situé hors du processus de l’app ne peut pas lire SwiftData. Le pont, c’est une couche de sérialisation (fichier JSON sur iCloud Drive, conteneur App Group, etc.) accessible à la fois au lecteur SwiftData interne à l’app et au serveur MCP hors processus.

Les trois substrats

Apple met à la disposition des apps iOS en production trois substrats de persistance natifs, ceux que l’on retrouve dans les architectures inter-processus et multi-appareils. Chacun a une forme précise ; les mélanger sans plan produit le problème de divergence.

SwiftData. Stockage persistant en processus, adossé à Core Data.2 Rapide, typé par le schéma, interrogeable via @Query, intégré au système d’observation de SwiftUI. Le magasin de données appartient au processus de l’app. Les extensions d’app (widgets, intents, extensions de partage) peuvent partager un conteneur SwiftData via un App Group moyennant une configuration explicite ; en revanche, un processus MCP externe arbitraire tournant sur la machine d’un développeur, hors du contexte de signature de l’app, ne peut pas atteindre le conteneur SwiftData en toute sécurité. Les lignes sont adressables via PersistentIdentifier (en processus) ou via des clés naturelles @Attribute(.unique) (inter-processus, multi-appareils). Les migrations sont déclaratives grâce à VersionedSchema et MigrationPlan (voir Le vrai coût de SwiftData, c’est la discipline de schéma).

iCloud Drive. Synchronisation multi-appareils orientée fichier, exposée par des URL FileManager dans le conteneur iCloud de l’utilisateur.3 Les fichiers apparaissent sur tous les appareils où l’utilisateur est connecté. La résolution de conflits se fait au niveau du fichier : iCloud s’appuie sur NSFileVersion pour suivre les modifications concurrentes, et l’app parcourt le journal des conflits pour décider quoi conserver. Les fichiers d’iCloud Drive sont adressables depuis l’extérieur du processus de l’app iOS : un serveur MCP sur Mac peut ouvrir le même fichier JSON que celui lu par l’app iOS. C’est ce substrat qui rend possible l’intégration MCP de Get Bananas.

NSUbiquitousKeyValueStore. Stockage clé-valeur multi-appareils. Les limites publiques actuelles d’Apple sont de 1 Mo au total par app, 1 Mo par valeur, 1 024 clés, 128 caractères UTF-16 par clé, avec un débit d’écriture bridé.4 La résolution de conflits est intégrée : le système sérialise les écritures et notifie tous les appareils au moindre changement. Adapté à un état réduit et peu fréquemment modifié (paramètres, dernier onglet sélectionné, compteur entier) ; inadapté aux données à fort taux d’écriture ou aux charges où le bridage devient le goulot d’étranglement. Return s’en sert pour l’état de minuteur partagé entre appareils (l’utilisateur lance un minuteur sur iPhone, le retrouve sur Apple Watch) ; le sujet est traité dans Cinq plateformes Apple, trois fichiers partagés.

Le quatrième substrat, CloudKit, est le choix qui semble évident et que les apps de cette série ont explicitement écarté pour l’intégration MCP inter-processus. CloudKit offre une synchronisation multi-appareils robuste avec des enregistrements qui gèrent les conflits, et Apple livre bien CloudKit JS et CloudKit Web Services pour permettre à des environnements non-Apple de dialoguer avec des bases CloudKit publiques et privées. Le compromis honnête porte sur le coût d’intégration, pas sur une impossibilité : un serveur MCP en Node.js qui veut atteindre une base CloudKit privée doit câbler l’authentification CloudKit Web Services, les définitions de schéma et la signature des requêtes, ce qui représente un travail d’ingénierie substantiel comparé à « ouvrir un fichier JSON ». Get Bananas a retenu iCloud Drive plus un fichier JSON parce que le serveur MCP est un processus Node.js qui doit lire et écrire les mêmes données que celles vues par l’app iOS, et que les entrées-sorties fichier ordinaires sont la voie de moindre résistance.1

La décision : quel substrat détient la vérité

La question n’est pas quel substrat utiliser. Elle est quel substrat fait autorité pour quel domaine. Les autres substrats se contentent de la mettre en cache, de la refléter, ou de s’effacer.

Voici la matrice de décision qui tient en production pour les apps de la série :

Forme du domaine Source de vérité Pourquoi
Paramètres, préférences, indicateurs simples NSUbiquitousKeyValueStore La synchronisation multi-appareils est automatique ; les collisions sont sérialisées ; la charge utile tient dans l’enveloppe
État transitoire propre à un appareil UserDefaults (sans synchronisation) Local à l’appareil ; ne doit pas survivre à une installation neuve sur un autre appareil
Collection interrogeable en processus SwiftData @Query rapide, observation SwiftUI, typage par le schéma ; en processus uniquement
Collection en processus devant atteindre des processus externes Fichier JSON sur iCloud Drive (export sur disque) Le lecteur SwiftData iOS comme le serveur MCP externe peuvent lire le fichier
Contenu volumineux propre à l’utilisateur (photos, audio, documents) iCloud Drive (fichier par fichier) L’iCloud de l’utilisateur est le magasin naturel ; CloudKit peut se superposer pour une synchronisation plus riche
État de session multi-appareils (minuteur lancé sur iPhone, consultable sur Watch) NSUbiquitousKeyValueStore Tient dans l’enveloppe de taille ; nécessite la sémantique de diffusion multi-appareils

Cette décision façonne la politique de résolution de conflits. Pour le pont entre l’app locale et le serveur MCP externe, SwiftData n’offre aucune résolution de conflits intrinsèque entre processus : si deux appelants écrivent dans la même ligne, c’est le dernier try context.save() qui l’emporte. SwiftData adossé à CloudKit et exploitant l’historique persistant peut porter une sémantique multi-appareils plus riche, mais cette surface reste côté iOS et ne résout rien pour le cas du serveur MCP Node.js externe. iCloud Drive, lui, expose les conflits sous forme d’entrées NSFileVersion ; à l’app de les parcourir et de désigner un gagnant. NSUbiquitousKeyValueStore intègre une résolution de conflits au niveau de la valeur.

L’architecture de Get Bananas

L’agencement réel de Get Bananas :

                     ┌────────────────────────────────────┐
                     │           User's mental model       │
                     │         "my shopping list"          │
                     └─────────────────┬──────────────────┘
                                       │
                          ┌────────────┴───────────┐
                          │                        │
                  ┌───────▼────────┐      ┌───────▼─────────┐
                  │   iOS app      │      │  MCP server      │
                  │  (Get Bananas) │      │  (Node.js)       │
                  └───────┬────────┘      └───────┬─────────┘
                          │                        │
              ┌───────────┴────────┐               │
              │                    │               │
       ┌──────▼──────┐    ┌────────▼──────────┐   │
       │  SwiftData   │    │  iCloud Drive     │◀──┘
       │ (in-process) │◀──▶│  shopping_list.   │
       │              │    │       json        │
       └──────────────┘    └───────────────────┘

  In-app reads/writes flow through SwiftData.
  Cross-process reads/writes flow through the JSON file.
  An iCloud sync layer (iCloudBackupManager) reconciles the two.

Cette architecture obéit à trois règles.

SwiftData fait autorité pour les requêtes en processus. L’app iOS lit dans SwiftData pour chaque rendu d’interface, chaque liste adossée à @Query, chaque recherche. Les écritures passent d’abord par SwiftData ; le contexte de modèle enregistre ; SwiftUI recalcule le rendu.

Le fichier JSON fait autorité pour l’état inter-processus. Chaque fois que l’app iOS enregistre dans SwiftData, un gestionnaire de sauvegarde iCloud exporte l’état courant vers un fichier JSON dans le conteneur iCloud Drive de l’utilisateur. Chaque fois que le serveur MCP écrit, il écrit dans ce même fichier JSON. Le fichier est le pont.

Une passe de synchronisation s’exécute au lancement de l’app iOS et après chaque écriture inter-processus. La logique de synchronisation actuellement en production (SyncManager.applyExport) traite la sauvegarde JSON comme faisant autorité à chaque passe : elle lit le fichier JSON, apparie chaque ligne à SwiftData par UUID, écrase les lignes existantes avec les valeurs de la sauvegarde, ajoute les lignes présentes dans la sauvegarde mais absentes de SwiftData, et supprime celles que SwiftData possède mais que la sauvegarde ignore (avec un garde-fou anti-corruption qui empêche un fichier de sauvegarde vide d’effacer la base locale). La politique est donc la sauvegarde l’emporte au moment de la synchronisation, et non un « dernier qui écrit gagne » ligne par ligne fondé sur l’horodatage. Combinée au réexport effectué par l’app iOS après chaque enregistrement, la convergence en régime établi est rapide dans les faits : le processus qui a écrit le plus récemment a produit le JSON que lira la synchronisation suivante.

L’architecture échange de la complexité contre une portée inter-processus. Une app purement SwiftData n’a besoin de rien de tout cela ; une app sans serveur MCP n’a pas besoin du pont JSON ; une app sans synchronisation multi-appareils n’a pas besoin du réconciliateur. Get Bananas a besoin des trois, parce que les trois classes d’appelants (l’humain via iOS, Apple Intelligence via App Intents sur iOS, l’agent via MCP depuis la machine Mac d’un développeur) touchent toutes la même liste de courses.

La voie d’amélioration : dernier écrivain gagnant ligne par ligne

La politique livrée — « la sauvegarde l’emporte au moment de la synchronisation » — est peu coûteuse et fonctionne pour le cas mono-utilisateur avec un seul écrivain actif à la fois. Elle peine dès que l’app iOS et le serveur MCP écrivent tous deux dans le fichier JSON à peu d’intervalle : le processus qui a écrit en dernier écrase les modifications de l’autre, y compris pour des lignes qui n’étaient pas réellement en conflit. L’atténuation actuelle tient au réexport effectué par l’app iOS après chaque enregistrement SwiftData, qui maintient le fichier JSON à peu près aligné sur l’état interne le plus récent. En régime établi, tout va bien ; c’est le cas véritablement concurrent qui peut faire perdre du travail.

L’amélioration la moins coûteuse consiste à passer à un « dernier écrivain gagnant » ligne par ligne, indexé sur une colonne lastModified: Date?. Le modèle ShoppingItem porte déjà ce champ par sécurité de migration (voir Le vrai coût de SwiftData, c’est la discipline de schéma), mais ni l’export JSON ni le serveur MCP ne le sérialisent ni ne l’honorent aujourd’hui. Faire circuler lastModified dans l’export et dans applyExport ferait passer la politique de fusion de « la sauvegarde l’emporte » à « la ligne la plus récente l’emporte » :

  • Les deux côtés ont une valeur, l’une est plus récente. La plus récente l’emporte. La ligne de l’autre côté est mise à jour.
  • Les deux côtés ont une valeur, à égalité. Départage par clé primaire, ou par surface (l’app iOS remporte les égalités, pour privilégier l’interaction la plus récente de l’utilisateur dans l’app).
  • Un seul côté a une valeur. Celui qui a la valeur l’emporte.
  • Aucun des deux n’a de valeur. Les deux lignes datent d’avant lastModified. Le réconciliateur y appose Date() pour la fois suivante.

Cette politique est peu coûteuse, facile à appréhender, et fausse dans environ 1 % des cas (modifications concurrentes de champs différents d’une même ligne). Pour une liste de courses, ce 1 % est sans conséquence ; pour un éditeur de documents, il est rédhibitoire. Les apps qui exigent des garanties plus fortes superposent à cette base une fusion au niveau du champ, des CRDT ou des transformées opérationnelles ; Get Bananas n’a pas encore eu besoin de cette complexité, d’où un LWW ligne par ligne inscrit à la feuille de route et une fusion plus riche qui ne l’est pas.

Les trois classes d’appelants et le substrat

Projetons les classes d’appelants de Trois surfaces sur les décisions de substrat :

La surface humaine écrit via SwiftData. Un utilisateur qui coche une case dans l’app iOS déclenche, à travers la couche SwiftUI, une fonction métier qui modifie la ligne SwiftData, appose lastModified = Date() sur le modèle et enregistre le contexte de modèle. L’export iCloud écrit l’état courant dans le fichier JSON. Le serveur MCP récupère le nouvel état à sa lecture suivante.

La surface Apple Intelligence écrit via SwiftData. Un AppIntent invoqué par Siri s’exécute dans le processus de l’app iOS et atteint la même fonction métier que la surface humaine. L’état SwiftData change, le lastModified du modèle se met à jour, et l’export JSON capture le nouvel état.

La surface agent écrit via le fichier JSON. Un appel d’outil MCP depuis une session Claude Code sur Mac modifie directement le fichier JSON (avec verrouillage de fichier pour gérer les écritures concurrentes en provenance de l’app iOS). Au lancement ou à la synchronisation suivante de l’app iOS, SyncManager.applyExport lit le fichier, parcourt les éléments par UUID, met à jour depuis les valeurs de la sauvegarde les lignes présentes des deux côtés, ajoute celles que la sauvegarde possède, et supprime celles qu’elle omet (avec le garde-fou anti-sauvegarde-vide). La politique livrée est la sauvegarde l’emporte au moment de la synchronisation ; la voie d’amélioration ajoute lastModified au JSON pour que la politique bascule vers la ligne la plus récente l’emporte.

L’asymétrie est réelle et voulue. Les surfaces humaine et Apple Intelligence s’exécutent toutes deux dans le processus de l’app iOS et utilisent SwiftData nativement. La surface agent s’exécute hors du processus de l’app iOS et utilise le fichier JSON parce que c’est le substrat qu’elle peut atteindre. Le réconciliateur, c’est ce qui tient les deux moitiés ensemble.

Quand cette approche est la mauvaise réponse

Quelques cas où le pont JSON n’est pas la bonne réponse.

Données à fort taux d’écriture. Un éditeur de documents en direct, avec de nombreuses modifications par seconde, ne peut pas assumer le coût d’une sérialisation de toute la collection vers un fichier JSON à chaque écriture. La bonne réponse, ce sont les transformées opérationnelles ou les CRDT contre un véritable back-end.

Exigences de cohérence forte. Un registre de transactions financières ne peut pas tolérer un « dernier qui écrit gagne » sur le fichier JSON. La bonne réponse, c’est CloudKit (ou une base de données côté serveur) avec une sémantique transactionnelle explicite.

Collaboration multi-utilisateurs où chacun voit les modifications des autres en temps réel. La synchronisation iCloud Drive est à cohérence éventuelle, pas en temps réel. L’utilisateur qui ferme l’app sur un appareil et l’ouvre sur un autre voit l’état qui s’est synchronisé ; celui qui voudrait voir le curseur d’un autre utilisateur se déplacer dans un document, non. La bonne réponse, c’est un cadre de collaboration en temps réel (yjs, automerge, ou une couche WebSocket sur mesure).

Cas où l’agent et l’utilisateur sont deux identités distinctes. L’approche de Get Bananas suppose que l’agent (l’appelant MCP) et l’utilisateur humain (celui de l’app iOS) sont la même personne, opérant simplement à travers plusieurs processus. Si l’agent agit pour le compte d’une identité différente (liste partagée, administrateur, robot automatisé), le fichier JSON posé dans l’iCloud Drive de l’utilisateur est le mauvais substrat ; il faut alors une persistance multi-utilisateurs avec authentification explicite.

L’approche convient au cas mono-utilisateur, à cohérence éventuelle, et inter-processus. La plupart des apps dotées d’intégrations MCP relèvent exactement de ce cas ; certaines non.

Ce que je construirais autrement

Trois approches que les apps de la série ont soit livrées, soit regretté de ne pas avoir livrées.

Rendre la sérialisation JSON explicite, pas implicite. La première version de Get Bananas exportait chaque écriture SwiftData vers le JSON dans un point d’accroche d’enregistrement. La deuxième a fait de l’export une étape explicite que l’app déclenche une fois l’état stabilisé. Le changement a réduit les écritures redondantes et rendu limpide le moment où l’état inter-processus était publié. Un point d’accroche implicite déclenché à chaque mutation produit trop d’entrées-sorties pour toute collection non triviale.

Versionner le schéma du fichier JSON. Le fichier JSON possède son propre schéma, indépendant du VersionedSchema de SwiftData. Quand le schéma SwiftData évolue (l’ajout d’un champ, par exemple), la sérialisation JSON doit suivre. Le correctif le moins coûteux consiste à placer un champ schemaVersion: Int en tête du JSON ; le réconciliateur le lit et applique la bonne interprétation. Sans versionnement, une app iOS v2 qui lit un fichier JSON v1 écrit par un ancien serveur MCP provoque une corruption silencieuse des données.

Verrouiller le fichier JSON, ne pas présumer d’une coordination. L’app iOS et le serveur MCP écrivent tous deux dans le fichier JSON. Sans NSFileCoordinator (en processus, côté iOS) et sans verrou de fichier (hors processus, sur la machine du développeur), des écritures concurrentes peuvent produire un fichier corrompu. Le serveur MCP de Get Bananas pose un verrou de type flock sur le JSON ; l’app iOS passe par NSFileCoordinator pour ses écritures ; les accès concurrents au fichier sont rares dans la pratique, mais la ceinture de sécurité ne coûte pas cher.

Ce que cette approche implique pour les apps livrées sur iOS 26+

Trois enseignements.

  1. Une seule source de vérité par domaine. Les autres substrats mettent en cache, reflètent, ou s’effacent. SwiftData pour les requêtes en processus, un JSON sur iCloud Drive pour les ponts inter-processus, NSUbiquitousKeyValueStore pour les petits états multi-appareils. La résolution de conflits est une conséquence en aval de ce choix.

  2. lastModified associé au « dernier qui écrit gagne » est le cas de base le moins coûteux. La plupart des apps n’ont pas besoin de garanties plus fortes. Les rares cas — environ 1 % — qui réclament une fusion au niveau du champ ou des CRDT coûtent cher à ajouter ; n’en payez le prix que lorsque la forme des données l’exige.

  3. Le réconciliateur est la pièce porteuse. Quand SwiftData et le fichier JSON divergent, c’est le réconciliateur qui tranche. Il s’exécute au lancement de l’app, après les écritures inter-processus et après les événements de synchronisation iCloud. Les règles sont simples ; toute la discipline consiste à l’exécuter réellement.

La série Écosystème Apple au complet : les App Intents typés pour la surface Apple Intelligence ; les serveurs MCP pour la surface agent ; la question de l’aiguillage entre les deux ; Foundation Models pour les fonctionnalités LLM embarquées dans l’app ; la distinction entre LLM d’exécution et LLM d’outillage ; la synthèse sur les trois surfaces d’une app iOS ; les Live Activities pour l’automate de l’écran verrouillé iOS ; le contrat de l’environnement d’exécution watchOS sur Apple Watch ; les rouages internes de SwiftUI pour le substrat de la surface humaine ; le modèle mental spatial de RealityKit pour les scènes visionOS ; la discipline de schéma SwiftData pour la persistance ; les motifs Liquid Glass pour la couche visuelle ; la livraison multiplateforme pour la portée multi-appareils. Le point d’entrée se trouve sur la série Écosystème Apple. Pour un contexte plus large sur iOS et les agents IA, consultez le guide Développement d’agents iOS.

FAQ

Pourquoi ne pas utiliser CloudKit pour la synchronisation inter-processus ?

CloudKit offre une synchronisation multi-appareils robuste avec des enregistrements qui gèrent les conflits, et CloudKit JS / CloudKit Web Services permettent bien à des piles non-Apple d’atteindre une base CloudKit privée. La contrainte, c’est le coût d’intégration : un serveur MCP en Node.js passant par CloudKit doit gérer l’authentification CloudKit (clés serveur-à-serveur ou jetons au niveau utilisateur), les déclarations de schéma et la signature des requêtes. iCloud Drive plus un fichier JSON, ce sont des entrées-sorties fichier ordinaires, autrement dit le traducteur universel. CloudKit est le bon choix quand l’équipe accepte d’en payer le coût d’intégration et veut sa synchronisation et sa sémantique de conflits plus solides ; le pont JSON est le bon choix quand « ouvrir un fichier » suffit à la forme des données.

Comment gérez-vous les conflits quand deux appelants écrivent en même temps ?

La politique livrée dans Get Bananas est « la sauvegarde l’emporte au moment de la synchronisation » : SyncManager.applyExport parcourt les éléments par UUID et écrase les lignes locales à partir de la sauvegarde, avec un garde-fou empêchant qu’une sauvegarde vide n’efface de bonnes données locales. La voie d’amélioration, c’est un « dernier écrivain gagnant » ligne par ligne indexé sur lastModified, champ que le modèle porte déjà mais qui n’est pas encore sérialisé à travers le pont JSON. L’ajouter réglerait les quelque 99 % de conflits où un côté est réellement plus récent ; le 1 % restant (modifications concurrentes de champs différents d’une même ligne) reste assez rare pour que les apps concernées se passent jusqu’ici de fusion au niveau du champ ou de CRDT. Les apps aux exigences de cohérence plus strictes superposent une fusion plus riche.

Où se situe SwiftData si iCloud Drive fait autorité pour l’état inter-processus ?

SwiftData fait autorité pour les requêtes en processus. L’app iOS lit dans SwiftData pour chaque rendu d’interface, chaque @Query, chaque recherche. SwiftData est rapide, typé par le schéma, et intégré au système d’observation de SwiftUI. Quand l’app iOS écrit, la modification part d’abord dans SwiftData, puis est exportée vers le fichier JSON. Le fichier JSON fait autorité pour les lectures inter-processus (la vue du serveur MCP) ; SwiftData fait autorité pour les lectures en processus (la vue de l’interface iOS). Le réconciliateur les maintient alignés.

Et pourquoi pas NSUbiquitousKeyValueStore pour la liste de courses elle-même ?

NSUbiquitousKeyValueStore est plafonné à 1 Mo au total par app et 1 Mo par valeur, avec des écritures bridées, et il sérialise un dictionnaire de 1 024 clés. Une liste de courses de plusieurs centaines d’éléments assortie d’un historique peut tenir en volume d’octets, mais faire passer les modifications élément par élément à travers le bridage n’est pas la bonne approche ; les mises à jour en masse d’une collection entrent alors en concurrence, sur le budget de débit, avec tout ce que l’app stocke par ailleurs. Le bon substrat pour les collections, c’est SwiftData (en processus) ou iCloud Drive (inter-processus). Réservez NSUbiquitousKeyValueStore aux petits états clé-valeur peu fréquemment modifiés : paramètres, dernier onglet sélectionné, compteur, dérogation à un indicateur de fonctionnalité.

Comment savoir quel substrat choisir pour un nouveau domaine dans mon app ?

Trois questions, dans l’ordre. Premièrement : quelque chose hors du processus de l’app iOS a-t-il besoin de lire ou d’écrire ce domaine ? Si oui, il vous faut iCloud Drive (orienté fichier, entrées-sorties ordinaires), CloudKit (via les frameworks Apple ou via CloudKit Web Services depuis des piles non-Apple), ou un serveur que vous contrôlez. Si non, SwiftData est le choix par défaut. Deuxièmement : cela doit-il se synchroniser entre les appareils de l’utilisateur ? Si oui, le substrat doit le permettre (iCloud Drive le fait, SwiftData non, sauf s’il est couplé à la synchronisation iCloud). Troisièmement : quelle est la taille de la charge utile et à quelle fréquence change-t-elle ? Petite et peu fréquente : NSUbiquitousKeyValueStore ; tout le reste réclame une véritable couche de persistance.

Références


  1. Analyse de l’auteur dans Deux écosystèmes d’agents, une seule liste de courses, 29 avril 2026, et couche de synchronisation JSON sur iCloud Drive du projet Get Bananas, dans Banana List/iCloudBackupManager.swift. L’architecture associe SwiftData à un fichier JSON placé dans le conteneur iCloud Drive de l’utilisateur, qu’un serveur MCP externe lit et écrit. 

  2. Apple Developer, « SwiftData » et « Adding and editing persistent data in your app ». La macro @Model, les contraintes @Attribute et la parenté avec le NSManagedObjectModel de Core Data. L’analyse de l’auteur dans Le vrai coût de SwiftData, c’est la discipline de schéma traite de VersionedSchema et MigrationPlan pour une évolution de schéma sans risque. 

  3. Apple Developer, « Synchronizing documents in the iCloud environment ». Synchronisation multi-appareils orientée fichier, résolution de conflits via NSFileVersion, et l’API NSFileCoordinator pour des écritures en processus sûres sur des fichiers partagés. 

  4. Apple Developer, « NSUbiquitousKeyValueStore ». Stockage clé-valeur multi-appareils. Limites actuelles d’Apple : 1 Mo au total par app, 1 Mo par valeur, 1 024 clés, 128 caractères UTF-16 par clé, débit d’écriture bridé. L’analyse de l’auteur dans Cinq plateformes Apple, trois fichiers partagés traite de l’approche de minuteur multi-appareils que Return livre en s’appuyant sur cette API. 

Articles connexes

Serveur MCP aux côtés d'une application iOS : deux écosystèmes d'agents, une seule liste

Get Bananas tourne sur iOS, macOS, watchOS et visionOS, et vit dans Claude Desktop comme serveur MCP. Le pont : iCloud D…

23 min de lecture

App Intents vs MCP : la question du routage

Deux protocoles, une seule app. Les App Intents exposent votre app à Apple Intelligence. MCP expose le même domaine à Cl…

23 min de lecture

Votre agent a un intermédiaire que vous n'avez pas vérifié

Sur 28 routeurs LLM API testés, 17 ont touché aux identifiants canari AWS. La couche routeur est une surface d'attaque.

18 min de lecture