Single Source Of Truth: SwiftData, MCP, iCloud
TITLE : Source unique de vérité : SwiftData, TERM_17, iCloud
DESCRIPTION : Trois appelants peuvent écrire dans la même liste de courses : un humain, Apple Intelligence et un agent externe. La vérité doit résider quelque part. Choisissez le substrat.
BODY:
Get Bananas a trois appelants qui peuvent écrire dans la même liste de courses. Un humain qui tape sur des lignes dans l’application iOS. Apple Intelligence qui achemine une requête Siri à travers un AppIntent. Une session TERM_0 qui appelle un outil TERM_17 via stdio. La liste est une chose canonique unique dans la tête de l’utilisateur ; la question est de savoir où elle réside dans le stockage et quel appelant l’emporte lorsqu’ils sont en désaccord.
Le billet de synthèse a nommé les trois surfaces d’une application iOS : humain, Apple Intelligence, agent. Chaque surface doit lire et écrire le même état du domaine. Cette exigence est ce qui produit l’erreur architecturale qui apparaît dans trop d’applications : chaque surface obtient son propre magasin, les surfaces dérivent et l’utilisateur voit trois versions différentes de sa liste selon celle qu’il a touchée en dernier. Le motif qui survit est une source unique de vérité avec des chemins de synchronisation explicites entre les surfaces et le substrat.
Ce billet nomme les choix de substrat, les règles de résolution de conflit que chacun impose et l’architecture qui tient lorsque les trois classes d’appelants peuvent écrire. La présentation utilise la disposition réelle de Get Bananas : SwiftData pour l’état dans l’application, un fichier TERM_12 dans iCloud Drive pour la synchronisation entre processus, un serveur TERM_17 qui lit et écrit dans le même fichier depuis l’extérieur du bac à sable iOS.1
TL;DR
- Trois substrats se composent : SwiftData (intra-processus, rapide, à schéma typé), iCloud Drive (inter-processus, basé sur des fichiers, synchronisable),
NSUbiquitousKeyValueStore(inter-appareils, clé-valeur, charges utiles uniquement petites). - Choisissez quel substrat est la source de vérité par domaine. La résolution de conflit est une conséquence forcée du choix, pas un problème distinct.
- Les trois surfaces (humain, Apple Intelligence, agent) interagissent chacune avec le substrat à travers la couche de domaine. La couche de domaine est l’endroit où réside la politique de résolution de conflit.
- Un horodatage
lastModifiedsur chaque entité mutable est la primitive bon marché de résolution de conflit ; « le dernier qui écrit gagne » est la politique bon marché. Les applications qui ont besoin de garanties plus fortes les paient avec une logique de fusion explicite. - Un serveur TERM_17 en dehors du processus de l’application ne peut pas lire SwiftData. Le pont est une couche de sérialisation (fichier TERM_12 dans iCloud Drive, conteneur App Group, etc.) à laquelle peuvent parler à la fois le lecteur SwiftData dans l’application et le serveur TERM_17 hors processus.
Les trois substrats
Apple offre aux applications iOS publiées trois substrats de persistance natifs qui apparaissent dans les schémas inter-processus et inter-appareils. Chacun a une forme spécifique ; les mélanger sans plan produit le problème de dérive.
SwiftData. Stockage persistant intra-processus adossé à Core Data.2 Rapide, à schéma typé, interrogeable via @Query, intégré au système d’observation de SwiftUI. Le magasin est détenu par le processus de l’application. Les extensions d’application (widgets, intents, extensions de partage) peuvent partager un conteneur SwiftData via un App Group avec une configuration explicite ; un processus TERM_17 externe arbitraire s’exécutant sur la machine d’un développeur en dehors du contexte de signature de l’application ne peut pas atteindre en toute sécurité le conteneur SwiftData. Les lignes sont adressables via PersistentIdentifier (intra-processus) ou les clés naturelles @Attribute(.unique) (inter-processus, inter-appareils). Les migrations sont déclaratives via VersionedSchema et MigrationPlan (couvert dans Le vrai coût de SwiftData, c’est la discipline de schéma).
iCloud Drive. Synchronisation inter-appareils basée sur des fichiers, exposée via les URL de FileManager dans le conteneur iCloud de l’utilisateur.3 Les fichiers apparaissent sur chaque appareil sur lequel l’utilisateur est connecté. La résolution de conflit se fait au niveau du fichier : iCloud utilise NSFileVersion pour suivre les modifications concurrentes, et l’application lit à travers le journal de conflit pour décider quoi conserver. Les fichiers dans iCloud Drive sont adressables depuis l’extérieur du processus de l’application iOS : un serveur TERM_17 Mac peut ouvrir le même fichier TERM_12 que celui que l’application iOS lit. Le substrat est ce qui fait fonctionner l’intégration TERM_17 de Get Bananas.
NSUbiquitousKeyValueStore. Stockage clé-valeur inter-appareils. Les limites publiques actuelles d’Apple sont de 1 Mo au total par application, 1 Mo par valeur, 1024 clés, 128 caractères UTF-16 par clé, avec des taux d’écriture limités.4 La résolution de conflit est intégrée : le système sérialise les écritures et notifie tous les appareils en cas de changement. Approprié pour un état petit et peu fréquent (paramètres, dernier onglet sélectionné, un compteur entier) ; inapproprié pour des données à taux d’écriture élevé ou des charges de travail où la limitation devient le goulot d’étranglement. Return l’utilise pour l’état de minuteur inter-appareils (l’utilisateur démarre un minuteur sur l’iPhone, le voit sur l’Apple Watch) ; couvert dans Cinq plateformes Apple, trois fichiers partagés.
Le quatrième substrat, CloudKit, est le choix qui semble évident mais que les applications du cluster ont explicitement rejeté pour l’intégration TERM_17 inter-processus. CloudKit offre une synchronisation inter-appareils robuste avec des enregistrements conscients des conflits, et Apple livre bien CloudKit JS et CloudKit Web Services pour permettre aux environnements non-Apple de communiquer avec des bases de données CloudKit publiques et privées. Le compromis honnête est le coût d’intégration, pas l’impossibilité : un serveur TERM_17 Node.js qui atteint une base de données CloudKit privée doit câbler l’authentification CloudKit Web Services, les définitions de schéma et la signature de requêtes, ce qui représente un travail d’ingénierie matériel comparé à « ouvrir un fichier TERM_12 ». Get Bananas a choisi iCloud Drive plus un fichier TERM_12 parce que le serveur TERM_17 est un processus Node.js qui doit lire et écrire les mêmes données que l’application iOS voit, et que les E/S de fichiers ordinaires sont le chemin de moindre résistance.1
La décision : quel substrat détient la vérité
La question n’est pas quel substrat utiliser. La question est quel substrat est la source de vérité pour quel domaine. Les autres substrats soit le mettent en cache, soit le reflètent, soit ne gênent pas.
La matrice de décision qui survit en production pour les applications du cluster :
| Forme de domaine | Source de vérité | Pourquoi |
|---|---|---|
| Paramètres, préférences, indicateurs simples | NSUbiquitousKeyValueStore |
La synchronisation inter-appareils est automatique ; les collisions sont sérialisées ; la petite charge utile convient |
| État transitoire propre à l’appareil | UserDefaults (sans synchronisation) |
Local à l’appareil ; ne devrait pas survivre à une nouvelle installation sur un autre appareil |
| Collection interrogeable intra-processus | SwiftData | @Query rapide, observation SwiftUI, à schéma typé ; intra-processus uniquement |
| Collection intra-processus qui doit atteindre des processus externes | Fichier TERM_12 dans iCloud Drive (export sur disque) | Le lecteur SwiftData iOS et le serveur TERM_17 externe peuvent tous deux lire le fichier |
| Contenu volumineux par utilisateur (photos, audio, documents) | iCloud Drive (par fichier) | L’iCloud de l’utilisateur est le magasin naturel ; CloudKit peut se superposer pour une synchronisation plus riche |
| État de niveau session inter-appareils (minuteur tournant sur iPhone, visible sur Watch) | NSUbiquitousKeyValueStore |
S’inscrit dans l’enveloppe de taille ; nécessite la sémantique push inter-appareils |
La décision façonne la politique de résolution de conflit. Pour le pont application-locale-plus-TERM_17-externe, SwiftData n’a pas de résolution de conflit inhérente entre processus ; si deux appelants écrivent dans la même ligne, le dernier try context.save() gagne. SwiftData adossé à CloudKit et utilisant l’historique persistant peut porter une sémantique inter-appareils plus riche, mais cette surface est côté iOS et n’aide pas le cas du TERM_17 Node.js externe. iCloud Drive expose les conflits sous forme d’entrées NSFileVersion ; l’application doit les parcourir et choisir un gagnant. NSUbiquitousKeyValueStore a une résolution de conflit intégrée au niveau de la valeur.
L’architecture de Get Bananas
La disposition réelle de Get Bananas :
┌────────────────────────────────────┐
│ User's mental model │
│ "my shopping list" │
└─────────────────┬──────────────────┘
│
┌────────────┴───────────┐
│ │
┌───────▼────────┐ ┌───────▼─────────┐
│ iOS app │ │ __TERM_17__ 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 __TERM_12__ file.
An iCloud sync layer (iCloudBackupManager) reconciles the two.
L’architecture comporte trois règles.
SwiftData est la source de vérité pour les requêtes intra-processus. L’application iOS lit depuis SwiftData pour chaque rendu d’interface, chaque liste adossée à @Query, chaque recherche. Les écritures passent d’abord par SwiftData ; le contexte du modèle enregistre ; SwiftUI re-rend.
Le fichier TERM_12 est la source de vérité pour l’état inter-processus. Chaque fois que l’application iOS enregistre dans SwiftData, un gestionnaire de sauvegarde iCloud exporte l’état actuel vers un fichier TERM_12 dans le conteneur iCloud Drive de l’utilisateur. Chaque fois que le serveur TERM_17 écrit, il écrit dans le même fichier TERM_12. Le fichier est le pont.
Une passe de synchronisation s’exécute au lancement de l’application iOS et après chaque écriture inter-processus. La logique de synchronisation actuellement en production (SyncManager.applyExport) traite la sauvegarde TERM_12 comme faisant autorité à chaque passe : elle lit le fichier TERM_12, fait correspondre chaque ligne à SwiftData par UUID, écrase les lignes existantes avec les valeurs de la sauvegarde, ajoute les lignes que la sauvegarde possède mais que SwiftData n’a pas, et supprime les lignes que SwiftData possède mais que la sauvegarde n’a pas (avec une protection contre la corruption qui empêche un fichier de sauvegarde vide d’effacer la base de données locale). La politique est la sauvegarde gagne au moment de la synchronisation, pas le dernier qui écrit gagne par ligne en fonction de l’horodatage. Combinée à la réexportation de l’application iOS après chaque sauvegarde, la convergence en régime permanent est rapide en pratique : quel que soit le processus qui a écrit le plus récemment a produit le TERM_12 que la prochaine synchronisation lit.
L’architecture échange de la complexité contre une portée inter-processus. Une application SwiftData pure n’a besoin de rien de tout cela ; une application sans serveur TERM_17 n’a pas besoin du pont TERM_12 ; une application sans synchronisation inter-appareils n’a pas besoin du réconciliateur. Get Bananas a besoin des trois parce que les trois classes d’appelants (humain via iOS, Apple Intelligence via App Intents sur iOS, agent via TERM_17 depuis la machine d’un développeur Mac) touchent toutes la même liste de courses.
Le chemin de mise à niveau : dernier-qui-écrit-gagne par ligne
La politique livrée de « la sauvegarde gagne au moment de la synchronisation » est bon marché et fonctionne pour le cas mono-utilisateur, avec un seul écrivain actif à la fois. Elle peine quand l’application iOS et le serveur TERM_17 écrivent tous deux dans le fichier TERM_12 en succession rapprochée : quel que soit le processus qui a écrit le plus récemment écrase les modifications de l’autre, y compris pour les lignes qui n’étaient pas réellement en conflit. L’atténuation actuelle est la réexportation par l’application iOS après chaque sauvegarde SwiftData, ce qui maintient le fichier TERM_12 globalement aligné avec l’état le plus récent dans l’application. Le régime permanent va bien ; le cas véritablement concurrent peut perdre du travail.
La mise à niveau la moins coûteuse est le dernier-qui-écrit-gagne par ligne, fondé sur une colonne lastModified: Date?. Le modèle ShoppingItem a déjà ce champ pour la sécurité de migration (couvert dans Le vrai coût de SwiftData, c’est la discipline de schéma), mais l’export TERM_12 et le serveur TERM_17 ne le sérialisent ni ne le respectent actuellement. Faire passer lastModified dans l’export et dans applyExport changerait la politique de fusion de « la sauvegarde gagne » à « la ligne la plus récente gagne » :
- Les deux côtés ont une valeur, l’une est plus récente. La plus récente gagne. La ligne de l’autre côté est mise à jour.
- Les deux côtés ont une valeur, elles sont à égalité. Bris d’égalité par clé primaire, ou par surface (l’application iOS gagne les égalités pour favoriser l’interaction la plus récente de l’utilisateur dans l’application).
- Un côté a une valeur, l’autre non. Le côté avec la valeur gagne.
- Aucun côté n’a de valeur. Les deux lignes sont des données pré-ère-
lastModified. Le réconciliateur apposeDate()pour la prochaine fois.
La politique est bon marché, facile à raisonner, et erronée dans environ 1 % des cas (modifications concurrentes de différents champs de la même ligne). Pour une liste de courses, ce 1 % n’a pas d’importance ; pour un éditeur de documents, c’est absolument crucial. Les applications qui ont besoin de garanties plus fortes superposent une fusion au niveau du champ, des CRDT ou des transformations opérationnelles à cette base ; Get Bananas n’a pas encore eu besoin de cette complexité, c’est pourquoi le LWW par ligne est sur la feuille de route et pas une fusion plus riche.
Les trois classes d’appelants et le substrat
Cartographier les classes d’appelants depuis Trois surfaces sur les décisions de substrat :
La surface humaine écrit via SwiftData. Un utilisateur qui tape sur une case à cocher dans l’application iOS déclenche, à travers la couche SwiftUI, une fonction de domaine qui mute la ligne SwiftData, appose lastModified = Date() sur le modèle et enregistre le contexte du modèle. L’export iCloud écrit l’état actuel dans le fichier TERM_12. Le serveur TERM_17 récupère le nouvel état lors de sa prochaine lecture.
La surface Apple Intelligence écrit via SwiftData. Un AppIntent invoqué via Siri s’exécute dans le processus de l’application iOS et atteint la même fonction de domaine que la surface humaine utilise. L’état SwiftData mute, le lastModified du modèle se met à jour, et l’export TERM_12 capture le nouvel état.
La surface agent écrit via le fichier TERM_12. Un appel d’outil TERM_17 depuis une session TERM_0 sur un Mac mute directement le fichier TERM_12 (avec verrouillage de fichier pour gérer les écritures concurrentes depuis l’application iOS). La prochaine fois que l’application iOS se lance ou se synchronise, SyncManager.applyExport lit le fichier, parcourt les éléments par UUID, met à jour les lignes qui existent des deux côtés à partir des valeurs de la sauvegarde, ajoute les lignes que la sauvegarde possède et supprime les lignes que la sauvegarde omet (avec la protection contre la sauvegarde vide). La politique livrée est la sauvegarde gagne au moment de la synchronisation ; le chemin de mise à niveau ajoute lastModified au TERM_12 pour que la politique puisse passer à la ligne la plus récente gagne.
L’asymétrie est réelle et intentionnelle. Les surfaces humaine et Apple Intelligence s’exécutent toutes deux à l’intérieur du processus de l’application iOS et utilisent SwiftData nativement. La surface agent s’exécute en dehors du processus de l’application iOS et utilise le fichier TERM_12 parce que c’est le substrat qu’elle peut atteindre. Le réconciliateur est ce qui maintient les deux moitiés ensemble.
Quand ce motif est la mauvaise réponse
Quelques cas où le motif pont-TERM_12 est inapproprié.
Données à taux d’écriture élevé. Un éditeur de documents en direct avec de nombreuses modifications par seconde ne peut pas payer le coût de la sérialisation de toute la collection vers un fichier TERM_12 à chaque écriture. La bonne réponse est les transformations opérationnelles ou les CRDT contre un véritable backend.
Exigences de cohérence forte. Un grand livre de transactions financières ne peut pas tolérer un dernier-qui-écrit-gagne sur le fichier TERM_12. La bonne réponse est CloudKit (ou une base de données côté serveur) avec une sémantique de transaction explicite.
Collaboration multi-utilisateurs où les utilisateurs voient les modifications des autres en temps réel. La synchronisation iCloud Drive est éventuelle, pas en temps réel. L’utilisateur qui ferme l’application sur un appareil et l’ouvre sur un autre voit l’état qui s’est synchronisé ; l’utilisateur qui voit le curseur d’un autre utilisateur se déplacer dans un document, non. La bonne réponse est un cadre de collaboration en temps réel (yjs, automerge, ou une couche WebSocket personnalisée).
Cas où l’agent et l’utilisateur sont des identités différentes. Le motif Get Bananas suppose que l’agent (l’appelant TERM_17) et l’utilisateur humain (l’utilisateur de l’application iOS) sont la même personne, opérant simplement à travers des processus. Si l’agent agit au nom d’une identité différente (une liste partagée, un administrateur, un bot automatisé), le fichier TERM_12 dans l’iCloud Drive de l’utilisateur est le mauvais substrat ; une persistance multi-utilisateurs avec authentification explicite est requise.
Le motif convient au cas mono-utilisateur, à cohérence éventuelle, inter-processus. La plupart des applications avec des intégrations TERM_17 sont exactement ce cas ; certaines ne le sont pas.
Ce que je construirais différemment
Trois motifs que les applications du cluster ont soit livrés, soit auraient souhaité avoir.
Rendre la sérialisation TERM_12 explicite, pas implicite. La première version de Get Bananas exportait chaque écriture SwiftData en TERM_12 dans un crochet de sauvegarde. La deuxième version a fait de l’export une étape explicite que l’application appelle lorsque l’état s’est stabilisé. Le changement a réduit les écritures redondantes et clarifié quand l’état inter-processus avait été publié. Un crochet implicite de sauvegarde-à-chaque-mutation produit trop d’E/S pour toute collection non triviale.
Versionner le schéma du fichier TERM_12. Le fichier TERM_12 a son propre schéma indépendant du VersionedSchema de SwiftData. Lorsque le schéma SwiftData change (par exemple, en ajoutant un champ), la sérialisation TERM_12 doit suivre. La solution bon marché consiste à mettre un champ schemaVersion: Int en haut du TERM_12 ; le réconciliateur le lit et applique la bonne interprétation. Sans versionnement, une application iOS v2 lisant un fichier TERM_12 v1 écrit par un ancien serveur TERM_17 rencontre une corruption silencieuse des données.
Verrouiller le fichier TERM_12, ne pas supposer la coordination. L’application iOS et le serveur TERM_17 écrivent tous deux dans le fichier TERM_12. Sans un NSFileCoordinator (intra-processus, côté iOS) et un verrou de fichier (hors processus, sur la machine du développeur), les écritures concurrentes peuvent produire un fichier corrompu. Le serveur TERM_17 de Get Bananas utilise un verrou de fichier de style flock sur le TERM_12 ; l’application iOS utilise NSFileCoordinator pour ses écritures ; le fichier est rarement contesté en pratique, mais la ceinture de sécurité est bon marché.
Ce que ce motif signifie pour les applications publiées sur iOS 26+
Trois enseignements.
-
Choisissez une seule source de vérité par domaine. Les autres substrats mettent en cache, reflètent ou ne gênent pas. SwiftData pour les requêtes intra-processus, TERM_12 dans iCloud Drive pour les ponts inter-processus,
NSUbiquitousKeyValueStorepour le petit état inter-appareils. La résolution de conflit est une conséquence en aval du choix. -
lastModifiedplus le dernier-qui-écrit-gagne est le cas de base bon marché. La plupart des applications n’ont pas besoin de garanties plus fortes. Les 1 % de cas qui nécessitent une fusion au niveau du champ ou des CRDT sont coûteux à ajouter ; ne payez pas le coût tant que la forme des données ne l’exige pas. -
Le réconciliateur est la pièce porteuse. Lorsque SwiftData et le fichier TERM_12 sont en désaccord, le réconciliateur décide. Le réconciliateur s’exécute au lancement de l’application, après les écritures inter-processus et après les événements de synchronisation iCloud. Les règles sont simples ; la discipline consiste à effectivement l’exécuter.
Le cluster Écosystème Apple complet : App Intents typés pour la surface Apple Intelligence ; serveurs TERM_17 pour la surface agent ; la question du routage entre eux ; Foundation Models pour les fonctionnalités TERM_23 sur appareil dans l’application ; la distinction TERM_23 runtime vs outillage ; la synthèse des trois surfaces d’une application iOS ; Live Activities pour la machine à états de l’écran de verrouillage iOS ; le contrat d’exécution watchOS sur Apple Watch ; les 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 multi-plateforme pour la portée inter-appareils. Le hub se trouve à la série Écosystème Apple. Pour un contexte iOS-avec-agents-IA plus large, consultez le guide de développement d’agents iOS.
FAQ
Pourquoi ne pas utiliser CloudKit pour la synchronisation inter-processus ?
CloudKit offre une synchronisation inter-appareils robuste avec des enregistrements conscients des conflits, et CloudKit JS / CloudKit Web Services d’Apple permettent bien à des piles non-Apple d’atteindre une base de données CloudKit privée. La contrainte est le coût d’intégration : un serveur TERM_17 Node.js utilisant CloudKit doit gérer l’authentification de CloudKit (clés serveur-à-serveur ou jetons au niveau utilisateur), les déclarations de schéma et les requêtes signées. iCloud Drive plus un fichier TERM_12, c’est de l’E/S de fichiers ordinaire, qui est le traducteur universel. CloudKit est le bon choix lorsque l’équipe est prête à payer le coût d’intégration et veut la synchronisation et la sémantique de conflit plus fortes de CloudKit ; le pont TERM_12 est le bon choix lorsque « ouvrir un fichier » suffit pour la forme des données.
Comment gérez-vous les conflits lorsque deux appelants écrivent en même temps ?
La politique livrée de Get Bananas est « la sauvegarde gagne au moment de la synchronisation » : SyncManager.applyExport parcourt les éléments par UUID et écrase les lignes locales depuis la sauvegarde, avec une protection contre une sauvegarde vide qui effacerait de bonnes données locales. Le chemin de mise à niveau est le dernier-qui-écrit-gagne par ligne, fondé sur lastModified, que le modèle porte déjà mais qui n’est pas encore sérialisé à travers le pont TERM_12. L’ajouter résoudrait les ~99 % de conflits où un côté est véritablement plus récent ; le 1 % restant (modifications concurrentes de différents champs de la même ligne) est suffisamment rare pour que les applications jusqu’à présent sautent la fusion au niveau du champ ou les CRDT. Les applications avec des exigences de cohérence plus fortes superposent une fusion plus riche par-dessus.
Où s’inscrit SwiftData si iCloud Drive est la source de vérité pour l’état inter-processus ?
SwiftData est la source de vérité pour les requêtes intra-processus. L’application iOS lit SwiftData pour chaque rendu d’interface, chaque @Query, chaque recherche. SwiftData est rapide, à schéma typé et intégré au système d’observation de SwiftUI. Lorsque l’application iOS écrit, le changement va d’abord à SwiftData, puis est exporté vers le fichier TERM_12. Le fichier TERM_12 est la source de vérité pour les lectures inter-processus (la vue du serveur TERM_17) ; SwiftData est la source de vérité pour les lectures intra-processus (la vue de l’interface iOS). Ils restent alignés grâce au réconciliateur.
Qu’en est-il de NSUbiquitousKeyValueStore pour la liste de courses elle-même ?
NSUbiquitousKeyValueStore est plafonné à 1 Mo au total par application et 1 Mo par valeur avec des écritures limitées, et se sérialise sur un dictionnaire de 1024 clés. Une liste de courses avec des centaines d’éléments plus l’historique pourrait s’inscrire en nombre d’octets, mais écrire des changements par élément à travers la limitation est la mauvaise forme ; les mises à jour groupées de collections rivalisent pour le budget de débit avec tout le reste que l’application stocke. Le bon substrat pour les collections est soit SwiftData (intra-processus) soit iCloud Drive (inter-processus). Réservez NSUbiquitousKeyValueStore pour le petit état clé-valeur peu fréquent : paramètres, dernier onglet sélectionné, un compteur, une surcharge d’indicateur de fonctionnalité.
Comment savoir quel substrat choisir pour un nouveau domaine dans mon application ?
Trois questions dans l’ordre. Premièrement : est-ce que quelque chose en dehors du processus de l’application iOS doit lire ou écrire ce domaine ? Si oui, vous avez besoin d’iCloud Drive (basé sur des fichiers, E/S de fichiers ordinaires) ou de CloudKit (via les frameworks d’Apple ou CloudKit Web Services depuis des piles non-Apple) ou d’un serveur que vous contrôlez. Si non, SwiftData est le défaut. Deuxièmement : cela doit-il se synchroniser entre les appareils de l’utilisateur ? Si oui, le substrat doit le prendre en charge (iCloud Drive le fait, SwiftData ne le fait pas à moins d’être associé à la synchronisation iCloud). Troisièmement : quelle est la taille de la charge utile et à quelle fréquence change-t-elle ? Petite + faible fréquence vit dans NSUbiquitousKeyValueStore ; tout le reste a besoin d’une véritable couche de persistance.
Références
-
Analyse de l’auteur dans Deux écosystèmes d’agents, une liste de courses, 29 avril 2026, et la couche de synchronisation TERM_12 d’iCloud Drive du projet Get Bananas dans
Banana List/iCloudBackupManager.swift. L’architecture associe SwiftData à un fichier TERM_12 dans le conteneur iCloud Drive de l’utilisateur, qu’un serveur TERM_17 externe lit et écrit. ↩↩ -
Apple Developer, « SwiftData » et « Adding and editing persistent data in your app ». La macro
@Model, les contraintes@Attributeet la relation avec leNSManagedObjectModelde Core Data. L’analyse de l’auteur dans Le vrai coût de SwiftData, c’est la discipline de schéma couvreVersionedSchemaetMigrationPlanpour une évolution sûre du schéma. ↩ -
Apple Developer, « Synchronizing documents in the iCloud environment ». Synchronisation inter-appareils basée sur des fichiers, résolution de conflit via
NSFileVersionet la TERM_18NSFileCoordinatorpour des écritures intra-processus sûres contre des fichiers partagés. ↩ -
Apple Developer, « NSUbiquitousKeyValueStore ». Stockage clé-valeur inter-appareils. Limites actuelles d’Apple : 1 Mo au total par application, 1 Mo par valeur, 1024 clés, 128 caractères UTF-16 par clé, taux d’écriture limité. L’analyse de l’auteur dans Cinq plateformes Apple, trois fichiers partagés couvre le motif de minuteur inter-appareils que Return livre en utilisant cette TERM_18. ↩