← Alle Beitrage

Single Source Of Truth: SwiftData, MCP, iCloud

Get Bananas hat drei Aufrufer, die dieselbe Einkaufsliste schreiben können. Einen Menschen, der Zeilen in der iOS-App antippt. Apple Intelligence, das eine Siri-Anfrage über ein AppIntent weiterleitet. Eine Claude Code-Sitzung, die ein MCP-Tool über stdio aufruft. Die Liste ist im Kopf des Benutzers eine kanonische Sache; die Frage ist, wo sie im Speicher lebt und welcher Aufrufer gewinnt, wenn sie sich uneinig sind.

Der Synthese-Beitrag benannte die drei Oberflächen einer iOS-App: Mensch, Apple Intelligence, Agent. Jede Oberfläche muss denselben Domänenzustand lesen und schreiben. Diese Anforderung erzeugt den architektonischen Fehler, der in zu vielen Apps ausgeliefert wird: Jede Oberfläche bekommt ihren eigenen Speicher, die Oberflächen driften auseinander, und der Benutzer sieht je nachdem, welche er zuletzt berührt hat, drei verschiedene Versionen seiner Liste. Das Muster, das überlebt, ist eine einzige Quelle der Wahrheit mit expliziten Synchronisationspfaden zwischen den Oberflächen und dem Substrat.

Der Beitrag benennt die Substrat-Optionen, die Konfliktauflösungsregeln, die jede einzelne erzwingt, und die Architektur, die hält, wenn alle drei Aufruferklassen schreiben können. Die Erläuterung verwendet das tatsächliche Layout von Get Bananas: SwiftData für In-App-Zustand, eine iCloud-Drive-JSON-Datei für prozessübergreifende Synchronisation, einen MCP-Server, der dieselbe Datei von außerhalb der iOS-Sandbox liest und schreibt.1

TL;DR

  • Drei Substrate kombinieren sich: SwiftData (im Prozess, schnell, schema-typisiert), iCloud Drive (prozessübergreifend, dateibasiert, synchronisierbar), NSUbiquitousKeyValueStore (geräteübergreifend, Schlüssel-Wert, nur kleine Nutzdaten).
  • Wählen Sie pro Domäne, welches Substrat die Quelle der Wahrheit ist. Konfliktauflösung ist eine erzwungene Konsequenz der Wahl, kein separates Problem.
  • Die drei Oberflächen (Mensch, Apple Intelligence, Agent) interagieren jeweils über die Domänenschicht mit dem Substrat. Die Domänenschicht ist der Ort, an dem die Konfliktauflösungsrichtlinie lebt.
  • Ein lastModified-Zeitstempel auf jeder veränderbaren Entität ist die günstige Konfliktauflösungs-Primitive; „Last Writer Wins” ist die günstige Richtlinie. Apps, die stärkere Garantien benötigen, bezahlen sie mit expliziter Merge-Logik.
  • Ein MCP-Server außerhalb des App-Prozesses kann SwiftData nicht lesen. Die Brücke ist eine Serialisierungsschicht (JSON-Datei in iCloud Drive, App-Group-Container usw.), mit der sowohl der In-App-SwiftData-Leser als auch der prozessexterne MCP-Server sprechen können.

Die drei Substrate

Apple gibt ausgelieferten iOS-Apps drei native Persistenz-Substrate, die in prozess- und geräteübergreifenden Mustern auftauchen. Jedes hat eine spezifische Form; sie ohne Plan zu vermischen, erzeugt das Drift-Problem.

SwiftData. Persistenter In-Prozess-Speicher, gestützt auf Core Data.2 Schnell, schema-typisiert, abfragbar über @Query, integriert mit dem Beobachtungssystem von SwiftUI. Der Speicher gehört dem App-Prozess. App-Erweiterungen (Widgets, Intents, Share Extensions) können einen SwiftData-Container über eine App Group mit expliziter Konfiguration teilen; ein beliebiger externer MCP-Prozess, der auf einem Entwicklerrechner außerhalb des Signierungskontexts der App läuft, kann nicht sicher in den SwiftData-Container hineingreifen. Zeilen sind über PersistentIdentifier (im Prozess) oder @Attribute(.unique)-Naturschlüssel (prozessübergreifend, geräteübergreifend) adressierbar. Migrationen sind deklarativ über VersionedSchema und MigrationPlan (behandelt in SwiftData’s Real Cost Is Schema Discipline).

iCloud Drive. Dateibasierte geräteübergreifende Synchronisation, bereitgestellt über FileManager-URLs im iCloud-Container des Benutzers.3 Dateien erscheinen auf jedem Gerät, auf dem der Benutzer angemeldet ist. Konfliktauflösung erfolgt auf Dateiebene: iCloud verwendet NSFileVersion, um gleichzeitige Bearbeitungen zu verfolgen, und die App liest das Konfliktprotokoll, um zu entscheiden, was behalten wird. Dateien in iCloud Drive sind von außerhalb des iOS-App-Prozesses adressierbar: Ein Mac-MCP-Server kann dieselbe JSON-Datei öffnen, die die iOS-App liest. Das Substrat ist das, was die MCP-Integration von Get Bananas funktionieren lässt.

NSUbiquitousKeyValueStore. Geräteübergreifender Schlüssel-Wert-Speicher. Apples aktuelle öffentliche Grenzen sind 1 MB gesamt pro App, 1 MB pro Wert, 1024 Schlüssel, 128 UTF-16-Zeichen pro Schlüssel, mit gedrosselten Schreibraten.4 Konfliktauflösung ist eingebaut: Das System serialisiert Schreibvorgänge und benachrichtigt alle Geräte bei Änderungen. Geeignet für kleine, niedrigfrequente Zustände (Einstellungen, zuletzt ausgewählter Tab, ein Integer-Zähler); ungeeignet für Daten mit hoher Schreibrate oder Workloads, bei denen die Drosselung zum Engpass wird. Return verwendet ihn für geräteübergreifenden Timer-Zustand (der Benutzer startet einen Timer auf dem iPhone, sieht ihn auf der Apple Watch); behandelt in Five Apple Platforms, Three Shared Files.

Das vierte Substrat, CloudKit, ist die offensichtlich aussehende Wahl, die die Apps des Clusters für die prozessübergreifende MCP-Integration explizit abgelehnt haben. CloudKit bietet starke geräteübergreifende Synchronisation mit konfliktbewussten Datensätzen, und Apple liefert CloudKit JS und CloudKit Web Services, damit Nicht-Apple-Umgebungen mit öffentlichen und privaten CloudKit-Datenbanken sprechen können. Der ehrliche Tradeoff sind die Integrationskosten, nicht die Unmöglichkeit: Ein Node.js-MCP-Server, der eine private CloudKit-Datenbank erreicht, muss CloudKit-Web-Services-Authentifizierung, Schemadefinitionen und Request-Signierung verdrahten, was im Vergleich zu „eine JSON-Datei öffnen” erhebliche Engineering-Arbeit ist. Get Bananas wählte iCloud Drive plus eine JSON-Datei, weil der MCP-Server ein Node.js-Prozess ist, der dieselben Daten lesen und schreiben muss, die die iOS-App sieht, und gewöhnliches Datei-I/O ist der Pfad des geringsten Widerstands.1

Die Entscheidung: Welches Substrat hält die Wahrheit

Die Frage ist nicht, welches Substrat zu verwenden ist. Die Frage ist, welches Substrat die Quelle der Wahrheit für welche Domäne ist. Die anderen Substrate cachen sie entweder, spiegeln sie oder bleiben aus dem Weg.

Die Entscheidungsmatrix, die in der Produktion für Cluster-Apps überlebt:

Domänenform Quelle der Wahrheit Warum
Einstellungen, Präferenzen, einfache Flags NSUbiquitousKeyValueStore Geräteübergreifende Synchronisation ist automatisch; Kollisionen werden serialisiert; kleine Nutzdaten passen
Pro-Gerät-Übergangszustand UserDefaults (keine Synchronisation) Gerätelokal; sollte eine Neuinstallation auf einem anderen Gerät nicht überleben
Im Prozess abfragbare Sammlung SwiftData Schnelles @Query, SwiftUI-Beobachtung, schema-typisiert; nur im Prozess
Im-Prozess-Sammlung, die externe Prozesse erreichen muss iCloud-Drive-JSON-Datei (Export auf die Festplatte) Sowohl der iOS-SwiftData-Leser als auch der externe MCP-Server können die Datei lesen
Großer benutzerspezifischer Inhalt (Fotos, Audio, Dokumente) iCloud Drive (pro Datei) Die iCloud des Benutzers ist der natürliche Speicher; CloudKit kann für reichere Synchronisation darübergelegt werden
Geräteübergreifender Sitzungszustand (Timer läuft auf iPhone, sichtbar auf Watch) NSUbiquitousKeyValueStore Passt in den Größenrahmen; benötigt die geräteübergreifende Push-Semantik

Die Entscheidung formt die Konfliktauflösungsrichtlinie. Für die Brücke aus lokaler App und externem MCP hat SwiftData keine inhärente Konfliktauflösung zwischen Prozessen; wenn zwei Aufrufer in dieselbe Zeile schreiben, gewinnt das letzte try context.save(). SwiftData, das von CloudKit gestützt wird und persistente Historie verwendet, kann reichere geräteübergreifende Semantik tragen, aber diese Oberfläche ist iOS-seitig und hilft im externen Node.js-MCP-Fall nicht. iCloud Drive zeigt Konflikte als NSFileVersion-Einträge an; die App muss sie durchgehen und einen Gewinner auswählen. NSUbiquitousKeyValueStore hat eingebaute Konfliktauflösung auf Wert-Ebene.

Die Get-Bananas-Architektur

Das tatsächliche Layout von 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.

Die Architektur hat drei Regeln.

SwiftData ist die Quelle der Wahrheit für In-Prozess-Abfragen. Die iOS-App liest aus SwiftData für jedes UI-Rendering, jede @Query-gestützte Liste, jede Suche. Schreibvorgänge gehen zuerst durch SwiftData; der Modellkontext speichert; SwiftUI rendert neu.

Die JSON-Datei ist die Quelle der Wahrheit für prozessübergreifenden Zustand. Wann immer die iOS-App in SwiftData speichert, exportiert ein iCloud-Backup-Manager den aktuellen Zustand in eine JSON-Datei im iCloud-Drive-Container des Benutzers. Wann immer der MCP-Server schreibt, schreibt er in dieselbe JSON-Datei. Die Datei ist die Brücke.

Ein Synchronisationsdurchlauf läuft beim Start der iOS-App und nach jedem prozessübergreifenden Schreibvorgang. Die heute in Produktion befindliche Synchronisationslogik (SyncManager.applyExport) behandelt das JSON-Backup bei jedem Durchlauf als autoritativ: Sie liest die JSON-Datei, ordnet jede Zeile per UUID SwiftData zu, überschreibt vorhandene Zeilen mit den Werten des Backups, fügt Zeilen hinzu, die das Backup hat, SwiftData aber nicht, und löscht Zeilen, die SwiftData hat, das Backup aber nicht (mit einem Korruptionsschutz, der verhindert, dass eine leere Backup-Datei die lokale Datenbank löscht). Die Richtlinie ist Backup gewinnt zur Synchronisationszeit, nicht pro Zeile Last-Writer-Wins per Zeitstempel. In Kombination mit dem erneuten Export der iOS-App nach jedem Speichern ist die Konvergenz im Steady-State in der Praxis schnell: Welcher Prozess zuletzt geschrieben hat, hat die JSON produziert, die die nächste Synchronisation liest.

Die Architektur tauscht Komplexität gegen prozessübergreifende Reichweite. Eine reine SwiftData-App benötigt nichts davon; eine App ohne MCP-Server benötigt die JSON-Brücke nicht; eine App ohne geräteübergreifende Synchronisation benötigt den Reconciler nicht. Get Bananas benötigt alle drei, weil alle drei Aufruferklassen (Mensch über iOS, Apple Intelligence über App Intents auf iOS, Agent über MCP vom Mac eines Entwicklers) dieselbe Einkaufsliste berühren.

Der Upgrade-Pfad: Pro-Zeile Last-Writer-Wins

Die ausgelieferte Richtlinie „Backup gewinnt zur Synchronisationszeit” ist günstig und funktioniert für den Fall eines einzelnen Benutzers mit jeweils einem aktiven Schreiber. Sie kämpft, wenn sowohl die iOS-App als auch der MCP-Server in dichter Folge in die JSON-Datei schreiben: Welcher Prozess zuletzt geschrieben hat, überschreibt die Änderungen des anderen, einschließlich für Zeilen, die nicht tatsächlich in Konflikt standen. Die heutige Abmilderung besteht darin, dass die iOS-App nach jedem SwiftData-Speichern erneut exportiert, was die JSON-Datei grob mit dem aktuellsten In-App-Zustand abgeglichen hält. Steady State ist in Ordnung; der wirklich gleichzeitige Fall kann Arbeit verlieren.

Das günstigste Upgrade ist Pro-Zeile-Last-Writer-Wins, das auf einer lastModified: Date?-Spalte basiert. Das ShoppingItem-Modell hat das Feld bereits aus Migrationssicherheitsgründen (behandelt in SwiftData’s Real Cost Is Schema Discipline), aber der JSON-Export und der MCP-Server serialisieren oder berücksichtigen es derzeit nicht. lastModified durch den Export und durch applyExport zu führen, würde die Merge-Richtlinie von „Backup gewinnt” zu „neuere Zeile gewinnt” ändern:

  • Beide Seiten haben einen Wert, einer ist neuer. Der neueste gewinnt. Die Zeile der anderen Seite wird aktualisiert.
  • Beide Seiten haben einen Wert, sie sind gleich. Tie-Breaking nach Primärschlüssel oder nach Oberfläche (die iOS-App gewinnt Gleichstände, um die jüngste In-App-Interaktion des Benutzers zu bevorzugen).
  • Eine Seite hat einen Wert, die andere nicht. Die Seite mit dem Wert gewinnt.
  • Keine Seite hat einen Wert. Beide Zeilen sind Daten aus der Vor-lastModified-Ära. Der Reconciler stempelt Date() für das nächste Mal.

Die Richtlinie ist günstig, leicht nachzuvollziehen und in etwa 1 % der Fälle falsch (gleichzeitige Bearbeitungen verschiedener Felder derselben Zeile). Für eine Einkaufsliste spielt das 1 % keine Rolle; für einen Dokumenteneditor absolut. Apps, die stärkere Garantien benötigen, schichten Feld-Level-Merging, CRDTs oder operationale Transformationen auf diese Basis; Get Bananas hat diese Komplexität noch nicht benötigt, weshalb Pro-Zeile-LWW auf der Roadmap steht und reicheres Merging nicht.

Die drei Aufruferklassen und das Substrat

Abbildung der Aufruferklassen aus Three Surfaces auf die Substratentscheidungen:

Die menschliche Oberfläche schreibt durch SwiftData. Ein Benutzer, der ein Kontrollkästchen in der iOS-App antippt, löst durch die SwiftUI-Schicht eine Domänenfunktion aus, die die SwiftData-Zeile mutiert, lastModified = Date() auf dem Modell stempelt und den Modellkontext speichert. Der iCloud-Export schreibt den aktuellen Zustand in die JSON-Datei. Der MCP-Server greift den neuen Zustand bei seinem nächsten Lesevorgang auf.

Die Apple-Intelligence-Oberfläche schreibt durch SwiftData. Ein über Siri aufgerufenes AppIntent läuft im iOS-App-Prozess und erreicht dieselbe Domänenfunktion, die die menschliche Oberfläche verwendet. Der SwiftData-Zustand mutiert, das lastModified des Modells aktualisiert sich, und der JSON-Export erfasst den neuen Zustand.

Die Agent-Oberfläche schreibt durch die JSON-Datei. Ein MCP-Tool-Aufruf aus einer Claude Code-Sitzung auf einem Mac mutiert die JSON-Datei direkt (mit Datei-Locking, um gleichzeitige Schreibvorgänge der iOS-App zu handhaben). Wenn die iOS-App das nächste Mal startet oder synchronisiert, liest SyncManager.applyExport die Datei, durchläuft die Items per UUID, aktualisiert auf beiden Seiten existierende Zeilen mit den Werten des Backups, fügt Zeilen hinzu, die das Backup hat, und löscht Zeilen, die das Backup auslässt (mit dem Schutz für leere Backups). Die ausgelieferte Richtlinie ist Backup gewinnt zur Synchronisationszeit; der Upgrade-Pfad fügt der JSON lastModified hinzu, sodass die Richtlinie zu neuere Zeile gewinnt wechseln kann.

Die Asymmetrie ist real und beabsichtigt. Die menschliche und die Apple-Intelligence-Oberfläche laufen beide innerhalb des iOS-App-Prozesses und verwenden SwiftData nativ. Die Agent-Oberfläche läuft außerhalb des iOS-App-Prozesses und verwendet die JSON-Datei, weil das das Substrat ist, das sie erreichen kann. Der Reconciler ist das, was die beiden Hälften zusammenhält.

Wann dieses Muster die falsche Antwort ist

Einige Fälle, in denen das JSON-Brücken-Muster falsch ist.

Daten mit hoher Schreibrate. Ein Live-Dokumenteneditor mit vielen Bearbeitungen pro Sekunde kann die Kosten nicht tragen, die gesamte Sammlung bei jedem Schreibvorgang in eine JSON-Datei zu serialisieren. Die richtige Antwort sind operationale Transformationen oder CRDTs gegen ein echtes Backend.

Anforderungen an starke Konsistenz. Ein Hauptbuch für Finanztransaktionen kann Last-Writer-Wins auf der JSON-Datei nicht tolerieren. Die richtige Antwort ist CloudKit (oder eine serverseitige Datenbank) mit expliziter Transaktionssemantik.

Mehrbenutzer-Zusammenarbeit, bei der Benutzer die Bearbeitungen der anderen in Echtzeit sehen. Die iCloud-Drive-Synchronisation ist eventual, nicht in Echtzeit. Der Benutzer, der die App auf einem Gerät schließt und auf einem anderen öffnet, sieht den synchronisierten Zustand; der Benutzer, der den Cursor eines anderen Benutzers über ein Dokument wandern sieht, nicht. Die richtige Antwort ist ein Echtzeit-Kollaborations-Framework (yjs, automerge oder eine benutzerdefinierte WebSocket-Schicht).

Fälle, in denen der Agent und der Benutzer unterschiedliche Identitäten sind. Das Get-Bananas-Muster geht davon aus, dass der Agent (der MCP-Aufrufer) und der menschliche Benutzer (der iOS-App-Benutzer) dieselbe Person sind, die nur prozessübergreifend operiert. Wenn der Agent im Auftrag einer anderen Identität handelt (eine geteilte Liste, ein Administrator, ein automatisierter Bot), ist die JSON-Datei in der iCloud Drive des Benutzers das falsche Substrat; Mehrbenutzer-Persistenz mit expliziter Authentifizierung ist erforderlich.

Das Muster passt zum Fall des einzelnen Benutzers, eventually-consistent, prozessübergreifend. Die meisten Apps mit MCP-Integrationen sind genau dieser Fall; einige sind es nicht.

Was ich anders bauen würde

Drei Muster, die die Apps des Clusters entweder ausgeliefert haben oder sich gewünscht hätten.

Machen Sie die JSON-Serialisierung explizit, nicht implizit. Die erste Version von Get Bananas exportierte jeden SwiftData-Schreibvorgang in einem Speicher-Hook in JSON. Die zweite Version machte den Export zu einem expliziten Schritt, den die App aufruft, wenn sich der Zustand stabilisiert hat. Die Änderung reduzierte redundante Schreibvorgänge und machte deutlich, wann der prozessübergreifende Zustand veröffentlicht worden war. Ein impliziter Save-on-every-mutation-Hook erzeugt zu viel I/O für jede nicht triviale Sammlung.

Versionieren Sie das Schema der JSON-Datei. Die JSON-Datei hat ihr eigenes Schema, unabhängig vom VersionedSchema von SwiftData. Wenn sich das SwiftData-Schema ändert (etwa durch Hinzufügen eines Feldes), muss die JSON-Serialisierung folgen. Die günstige Lösung ist, ein schemaVersion: Int-Feld an den Anfang der JSON zu setzen; der Reconciler liest es und wendet die richtige Interpretation an. Ohne Versionierung trifft eine v2-iOS-App, die eine v1-JSON-Datei liest, die von einem alten MCP-Server geschrieben wurde, auf stille Datenkorruption.

Datei-locken Sie die JSON, gehen Sie nicht von Koordination aus. Die iOS-App und der MCP-Server schreiben beide in die JSON-Datei. Ohne einen NSFileCoordinator (im Prozess, iOS-seitig) und einen Datei-Lock (prozessextern, auf dem Entwicklerrechner) können gleichzeitige Schreibvorgänge eine korrupte Datei erzeugen. Der MCP-Server von Get Bananas verwendet einen Datei-Lock im flock-Stil auf der JSON; die iOS-App verwendet NSFileCoordinator für ihre Schreibvorgänge; die Datei wird in der Praxis selten umkämpft, aber der Sicherheitsgurt ist günstig.

Was das Muster für Apps bedeutet, die auf iOS 26+ ausgeliefert werden

Drei Erkenntnisse.

  1. Wählen Sie eine Quelle der Wahrheit pro Domäne. Die anderen Substrate cachen, spiegeln oder bleiben aus dem Weg. SwiftData für In-Prozess-Abfragen, iCloud-Drive-JSON für prozessübergreifende Brücken, NSUbiquitousKeyValueStore für kleinen geräteübergreifenden Zustand. Konfliktauflösung ist eine nachgelagerte Konsequenz der Wahl.

  2. lastModified plus Last-Writer-Wins ist der günstige Basisfall. Die meisten Apps benötigen keine stärkeren Garantien. Die 1 % der Fälle, die Feld-Level-Merging oder CRDTs benötigen, sind teuer hinzuzufügen; bezahlen Sie die Kosten nicht, bis die Datenform es verlangt.

  3. Der Reconciler ist das tragende Stück. Wenn SwiftData und die JSON-Datei nicht übereinstimmen, entscheidet der Reconciler. Der Reconciler läuft beim App-Start, nach prozessübergreifenden Schreibvorgängen und nach iCloud-Synchronisationsereignissen. Die Regeln sind einfach; die Disziplin besteht darin, ihn tatsächlich auszuführen.

Der vollständige Apple-Ecosystem-Cluster: typisierte App Intents für die Apple-Intelligence-Oberfläche; MCP-Server für die Agent-Oberfläche; die Routing-Frage zwischen ihnen; Foundation Models für In-App-On-Device-LLM-Funktionen; die Runtime-vs-Tooling-LLM-Unterscheidung; die drei-Oberflächen-einer-iOS-App-Synthese; Live Activities für die iOS-Lock-Screen-Zustandsmaschine; der watchOS-Runtime-Vertrag auf Apple Watch; SwiftUI internals für das Substrat der menschlichen Oberfläche; RealityKits räumliches mentales Modell für visionOS-Szenen; SwiftData-Schemadisziplin für Persistenz; Liquid-Glass-Patterns für die visuelle Schicht; Multi-Plattform-Auslieferung für geräteübergreifende Reichweite. Der Hub befindet sich in der Apple Ecosystem Series. Für breiteren iOS-mit-AI-Agenten-Kontext siehe den iOS Agent Development guide.

FAQ

Warum nicht CloudKit für prozessübergreifende Synchronisation verwenden?

CloudKit bietet starke geräteübergreifende Synchronisation mit konfliktbewussten Datensätzen, und Apples CloudKit JS / CloudKit Web Services lassen Nicht-Apple-Stacks tatsächlich eine private CloudKit-Datenbank erreichen. Die Einschränkung sind die Integrationskosten: Ein Node.js-MCP-Server, der CloudKit verwendet, muss CloudKits Authentifizierung (Server-zu-Server-Schlüssel oder Tokens auf Benutzerebene), Schemadeklarationen und signierte Anfragen handhaben. iCloud Drive plus eine JSON-Datei ist gewöhnliches Datei-I/O, was der universelle Übersetzer ist. CloudKit ist die richtige Wahl, wenn das Team bereit ist, die Integrationskosten zu zahlen, und CloudKits stärkere Sync- und Konfliktsemantik möchte; die JSON-Brücke ist die richtige Wahl, wenn „eine Datei öffnen” für die Datenform ausreicht.

Wie handhaben Sie Konflikte, wenn zwei Aufrufer gleichzeitig schreiben?

Die ausgelieferte Richtlinie von Get Bananas ist „Backup gewinnt zur Synchronisationszeit”: SyncManager.applyExport durchläuft Items per UUID und überschreibt lokale Zeilen aus dem Backup, mit einem Schutz dagegen, dass ein leeres Backup gute lokale Daten löscht. Der Upgrade-Pfad ist Pro-Zeile-Last-Writer-Wins, das auf lastModified basiert, was das Modell bereits trägt, aber noch nicht durch die JSON-Brücke serialisiert wird. Es hinzuzufügen würde ~99 % der Konflikte auflösen, bei denen eine Seite tatsächlich neuer ist; das verbleibende 1 % (gleichzeitige Bearbeitungen verschiedener Felder derselben Zeile) ist selten genug, dass die Apps bisher Feld-Level-Merging oder CRDTs überspringen können. Apps mit stärkeren Konsistenzanforderungen schichten reicheres Merging darauf.

Wo passt SwiftData hin, wenn iCloud Drive die Quelle der Wahrheit für prozessübergreifenden Zustand ist?

SwiftData ist die Quelle der Wahrheit für In-Prozess-Abfragen. Die iOS-App liest SwiftData für jedes UI-Rendering, jede @Query, jede Suche. SwiftData ist schnell, schema-typisiert und in das Beobachtungssystem von SwiftUI integriert. Wenn die iOS-App schreibt, geht die Änderung zuerst an SwiftData und wird dann in die JSON-Datei exportiert. Die JSON-Datei ist die Quelle der Wahrheit für prozessübergreifende Lesevorgänge (die Sicht des MCP-Servers); SwiftData ist die Quelle der Wahrheit für In-Prozess-Lesevorgänge (die Sicht der iOS-UI). Sie bleiben über den Reconciler abgeglichen.

Was ist mit NSUbiquitousKeyValueStore für die Einkaufsliste selbst?

NSUbiquitousKeyValueStore ist auf 1 MB gesamt pro App und 1 MB pro Wert mit gedrosselten Schreibvorgängen begrenzt und serialisiert auf einem Wörterbuch mit 1024 Schlüsseln. Eine Einkaufsliste mit Hunderten von Items plus Historie passt vielleicht nach Byte-Anzahl, aber Pro-Item-Änderungen durch die Drosselung zu schreiben, ist die falsche Form; Bulk-Sammlungs-Updates konkurrieren um das Ratenbudget gegen alles andere, was die App speichert. Das richtige Substrat für Sammlungen ist entweder SwiftData (im Prozess) oder iCloud Drive (prozessübergreifend). Reservieren Sie NSUbiquitousKeyValueStore für kleinen, niedrigfrequenten Schlüssel-Wert-Zustand: Einstellungen, den zuletzt ausgewählten Tab, einen Zähler, eine Feature-Flag-Überschreibung.

Woher weiß ich, welches Substrat ich für eine neue Domäne in meiner App auswählen soll?

Drei Fragen in Reihenfolge. Erstens: Muss irgendetwas außerhalb des iOS-App-Prozesses diese Domäne lesen oder schreiben? Wenn ja, benötigen Sie iCloud Drive (dateibasiert, gewöhnliches Datei-I/O) oder CloudKit (über Apples Frameworks oder CloudKit Web Services aus Nicht-Apple-Stacks) oder einen Server, den Sie kontrollieren. Wenn nein, ist SwiftData die Standardwahl. Zweitens: Muss dies geräteübergreifend für den Benutzer synchronisiert werden? Wenn ja, muss das Substrat das unterstützen (iCloud Drive tut es, SwiftData nicht, es sei denn, es ist mit iCloud-Synchronisation gepaart). Drittens: Wie groß sind die Nutzdaten und wie häufig ändern sie sich? Klein + niedrigfrequent lebt in NSUbiquitousKeyValueStore; alles andere benötigt eine echte Persistenzschicht.

Referenzen


  1. Analyse des Autors in Two Agent Ecosystems, One Shopping List, 29. April 2026, und die iCloud-Drive-JSON-Synchronisationsschicht des Get-Bananas-Projekts in Banana List/iCloudBackupManager.swift. Die Architektur paart SwiftData mit einer JSON-Datei im iCloud-Drive-Container des Benutzers, die ein externer MCP-Server liest und schreibt. 

  2. Apple Developer, “SwiftData” und “Adding and editing persistent data in your app”. Das @Model-Makro, @Attribute-Beschränkungen und die Beziehung zum NSManagedObjectModel von Core Data. Analyse des Autors in SwiftData’s Real Cost Is Schema Discipline behandelt VersionedSchema und MigrationPlan für sichere Schemaentwicklung. 

  3. Apple Developer, “Synchronizing documents in the iCloud environment”. Dateibasierte geräteübergreifende Synchronisation, Konfliktauflösung über NSFileVersion und das API NSFileCoordinator für sichere In-Prozess-Schreibvorgänge gegen geteilte Dateien. 

  4. Apple Developer, “NSUbiquitousKeyValueStore”. Geräteübergreifender Schlüssel-Wert-Speicher. Apples aktuelle Grenzen: 1 MB gesamt pro App, 1 MB pro Wert, 1024 Schlüssel, 128 UTF-16-Zeichen pro Schlüssel, gedrosselte Schreibrate. Analyse des Autors in Five Apple Platforms, Three Shared Files behandelt das geräteübergreifende Timer-Pattern, das Return mit diesem API ausliefert. 

Verwandte Beiträge

MCP-Server neben einer iOS-App: Zwei Agenten-Ökosysteme, eine Liste

Get Bananas läuft auf iOS, macOS, watchOS und visionOS. Es lebt zudem in Claude Desktop als MCP-Server. Brücke: iCloud D…

15 Min. Lesezeit

App Intents vs MCP: Die Frage des Routings

Zwei Protokolle, eine App. App Intents öffnen Ihre App für Apple Intelligence. MCP öffnet dieselbe Domäne für Claude, Ch…

12 Min. Lesezeit

Ihr Agent hat einen Mittelsmann, den Sie nicht geprüft haben

Forscher testeten 28 LLM API-Router. 17 griffen auf AWS-Canary-Credentials zu. Einer leerte ETH von einem Private Key. D…

12 Min. Lesezeit