← Wszystkie wpisy

Pojedyncze źródło prawdy: SwiftData, MCP, iCloud

Get Bananas ma trzech wywołujących, którzy mogą zapisywać do tej samej listy zakupów. Człowieka stukającego w wiersze w aplikacji iOS. Apple Intelligence kierujące żądanie Siri przez AppIntent. Sesję Claude Code wywołującą narzędzie MCP przez stdio. Lista jest jedną kanoniczną rzeczą w głowie użytkownika; pytanie brzmi, gdzie ona istnieje w pamięci masowej i który wywołujący wygrywa, gdy się nie zgadzają.

Post syntetyzujący nazwał trzy powierzchnie aplikacji iOS: człowiek, Apple Intelligence, agent. Każda powierzchnia musi czytać i zapisywać ten sam stan domeny. To wymaganie wytwarza błąd architektoniczny, który trafia do zbyt wielu aplikacji: każda powierzchnia dostaje własne magazyn, powierzchnie się rozjeżdżają, a użytkownik widzi trzy różne wersje swojej listy w zależności od tego, której dotknął jako ostatniej. Wzorzec, który przetrwał, to pojedyncze źródło prawdy z jawnymi ścieżkami synchronizacji między powierzchniami a podłożem.

Post nazywa wybory podłoża, reguły rozwiązywania konfliktów wymuszane przez każdy z nich oraz architekturę, która utrzymuje się, gdy wszystkie trzy klasy wywołujących mogą zapisywać. Omówienie wykorzystuje rzeczywisty układ Get Bananas: SwiftData dla stanu wewnątrz aplikacji, plik JSON w iCloud Drive dla synchronizacji międzyprocesowej oraz serwer MCP czytający i zapisujący ten sam plik spoza piaskownicy iOS.1

TL;DR

  • Trzy podłoża się składają: SwiftData (wewnątrz procesu, szybkie, typowane schematem), iCloud Drive (międzyprocesowe, oparte na plikach, synchronizowalne), NSUbiquitousKeyValueStore (między urządzeniami, klucz-wartość, tylko małe ładunki).
  • Należy wybrać, które podłoże jest źródłem prawdy dla każdej domeny. Rozwiązywanie konfliktów jest wymuszoną konsekwencją wyboru, a nie odrębnym problemem.
  • Trzy powierzchnie (człowiek, Apple Intelligence, agent) wchodzą w interakcję z podłożem przez warstwę domeny. Warstwa domeny to miejsce, w którym znajduje się polityka rozwiązywania konfliktów.
  • Znacznik czasu lastModified na każdym mutowalnym bycie jest tanim prymitywem rozwiązywania konfliktów; „ostatni piszący wygrywa” to tania polityka. Aplikacje wymagające silniejszych gwarancji płacą za nie jawną logiką scalania.
  • Serwer MCP poza procesem aplikacji nie może czytać SwiftData. Mostem jest warstwa serializacji (plik JSON w iCloud Drive, kontener App Group itp.), z którą mogą rozmawiać zarówno czytnik SwiftData wewnątrz aplikacji, jak i serwer MCP poza procesem.

Trzy podłoża

Apple udostępnia produkcyjnym aplikacjom iOS trzy natywne podłoża trwałości, które pojawiają się we wzorcach międzyprocesowych i międzyurządzeniowych. Każde ma określony kształt; mieszanie ich bez planu wytwarza problem rozjazdu.

SwiftData. Trwała pamięć wewnątrz procesu oparta na Core Data.2 Szybka, typowana schematem, możliwa do odpytywania przez @Query, zintegrowana z systemem obserwacji SwiftUI. Magazyn należy do procesu aplikacji. Rozszerzenia aplikacji (widgety, intencje, rozszerzenia udostępniania) mogą współdzielić kontener SwiftData przez App Group z jawną konfiguracją; arbitralny zewnętrzny proces MCP działający na maszynie programisty poza kontekstem podpisywania aplikacji nie może bezpiecznie sięgnąć do kontenera SwiftData. Wiersze są adresowalne przez PersistentIdentifier (wewnątrz procesu) lub naturalne klucze @Attribute(.unique) (międzyprocesowe, międzyurządzeniowe). Migracje są deklaratywne przez VersionedSchema i MigrationPlan (omówione w SwiftData’s Real Cost Is Schema Discipline).

iCloud Drive. Synchronizacja międzyurządzeniowa oparta na plikach, eksponowana przez adresy URL FileManager w kontenerze iCloud użytkownika.3 Pliki pojawiają się na każdym urządzeniu, do którego użytkownik jest zalogowany. Rozwiązywanie konfliktów odbywa się na poziomie pliku: iCloud używa NSFileVersion do śledzenia równoczesnych edycji, a aplikacja czyta dziennik konfliktów, aby zdecydować, co zachować. Pliki w iCloud Drive są adresowalne spoza procesu aplikacji iOS: serwer MCP na Macu może otworzyć ten sam plik JSON, który czyta aplikacja iOS. To podłoże sprawia, że integracja MCP w Get Bananas działa.

NSUbiquitousKeyValueStore. Pamięć klucz-wartość między urządzeniami. Aktualne publiczne limity Apple to 1MB łącznie na aplikację, 1MB na wartość, 1024 klucze, 128 znaków UTF-16 na klucz, z dławionymi szybkościami zapisu.4 Rozwiązywanie konfliktów jest wbudowane: system serializuje zapisy i powiadamia wszystkie urządzenia o zmianie. Odpowiedni dla małego stanu o niskiej częstotliwości (ustawienia, ostatnio wybrana karta, licznik całkowity); nieodpowiedni dla danych o wysokiej częstotliwości zapisu lub obciążeń, w których dławienie staje się wąskim gardłem. Return używa go dla stanu timera między urządzeniami (użytkownik uruchamia timer na iPhonie, widzi go na Apple Watch); omówione w Five Apple Platforms, Three Shared Files.

Czwartym podłożem, CloudKit, jest oczywisty z pozoru wybór, który aplikacje klastra jawnie odrzuciły dla integracji międzyprocesowej MCP. CloudKit zapewnia silną synchronizację międzyurządzeniową z rekordami świadomymi konfliktów, a Apple dostarcza CloudKit JS i CloudKit Web Services dla środowisk innych niż Apple, aby rozmawiać z publicznymi i prywatnymi bazami danych CloudKit. Uczciwy kompromis to koszt integracji, a nie niemożliwość: serwer MCP w Node.js sięgający do prywatnej bazy danych CloudKit musi okablować uwierzytelnianie CloudKit Web Services, definicje schematów i podpisywanie żądań, co stanowi istotną pracę inżynierską w porównaniu z „otwórz plik JSON”. Get Bananas wybrało iCloud Drive plus plik JSON, ponieważ serwer MCP to proces Node.js, który musi czytać i zapisywać te same dane, które widzi aplikacja iOS, a zwykłe wejście/wyjście plikowe jest ścieżką najmniejszego oporu.1

Decyzja: które podłoże przechowuje prawdę

Pytanie nie brzmi którego podłoża użyć. Pytanie brzmi które podłoże jest źródłem prawdy dla której domeny. Pozostałe podłoża albo ją buforują, albo lustrzanie odbijają, albo schodzą z drogi.

Macierz decyzyjna, która przetrwała produkcję dla aplikacji klastra:

Kształt domeny Źródło prawdy Dlaczego
Ustawienia, preferencje, proste flagi NSUbiquitousKeyValueStore Synchronizacja międzyurządzeniowa jest automatyczna; kolizje są serializowane; mały ładunek pasuje
Stan przejściowy specyficzny dla urządzenia UserDefaults (bez synchronizacji) Lokalny dla urządzenia; nie powinien przetrwać świeżej instalacji na innym urządzeniu
Kolekcja odpytywalna wewnątrz procesu SwiftData Szybkie @Query, obserwacja SwiftUI, typowana schematem; tylko wewnątrz procesu
Kolekcja wewnątrz procesu, która musi sięgać do procesów zewnętrznych Plik JSON w iCloud Drive (eksport na dysk) Zarówno czytnik SwiftData iOS, jak i zewnętrzny serwer MCP mogą czytać ten plik
Duża zawartość specyficzna dla użytkownika (zdjęcia, audio, dokumenty) iCloud Drive (na plik) iCloud użytkownika to naturalny magazyn; CloudKit może być nałożony na wierzch dla bogatszej synchronizacji
Stan na poziomie sesji między urządzeniami (timer działający na iPhonie, widoczny na Watch) NSUbiquitousKeyValueStore Mieści się w kopercie rozmiaru; potrzebuje semantyki push między urządzeniami

Decyzja kształtuje politykę rozwiązywania konfliktów. Dla mostu lokalna-aplikacja-plus-zewnętrzny-MCP SwiftData nie ma nieodłącznego rozwiązywania konfliktów między procesami; jeśli dwóch wywołujących pisze do tego samego wiersza, wygrywa ostatnie try context.save(). SwiftData oparte na CloudKit i używające historii trwałej może nieść bogatszą semantykę międzyurządzeniową, ale ta powierzchnia jest po stronie iOS i nie pomaga w przypadku zewnętrznego Node.js MCP. iCloud Drive eksponuje konflikty jako wpisy NSFileVersion; aplikacja musi je przejść i wybrać zwycięzcę. NSUbiquitousKeyValueStore ma wbudowane rozwiązywanie konfliktów na poziomie wartości.

Architektura Get Bananas

Rzeczywisty układ 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.

Architektura ma trzy zasady.

SwiftData jest źródłem prawdy dla zapytań wewnątrz procesu. Aplikacja iOS czyta z SwiftData dla każdego renderowania interfejsu, każdej listy opartej na @Query, każdego wyszukiwania. Zapisy idą najpierw przez SwiftData; kontekst modelu zapisuje; SwiftUI ponownie renderuje.

Plik JSON jest źródłem prawdy dla stanu międzyprocesowego. Ilekroć aplikacja iOS zapisuje do SwiftData, menedżer kopii zapasowej iCloud eksportuje bieżący stan do pliku JSON w kontenerze iCloud Drive użytkownika. Ilekroć serwer MCP pisze, pisze do tego samego pliku JSON. Plik jest mostem.

Przebieg synchronizacji uruchamia się przy starcie aplikacji iOS i po każdym zapisie międzyprocesowym. Logika synchronizacji obecnie w produkcji (SyncManager.applyExport) traktuje kopię zapasową JSON jako autorytatywną w każdym przebiegu: czyta plik JSON, dopasowuje każdy wiersz do SwiftData po UUID, nadpisuje istniejące wiersze wartościami z kopii zapasowej, dodaje wiersze, które kopia zapasowa ma, a SwiftData nie, i usuwa wiersze, które SwiftData ma, a kopia zapasowa nie (z zabezpieczeniem przed uszkodzeniem chroniącym przed pustym plikiem kopii zapasowej wymazującym lokalną bazę danych). Polityka to kopia zapasowa wygrywa w czasie synchronizacji, a nie ostatni piszący wygrywa per wiersz po znaczniku czasu. W połączeniu z aplikacją iOS ponownie eksportującą po każdym zapisie, zbieżność stanu ustalonego jest w praktyce szybka: który proces zapisał ostatnio, wytworzył JSON, który następna synchronizacja czyta.

Architektura wymienia złożoność na zasięg międzyprocesowy. Czysta aplikacja SwiftData nie potrzebuje niczego z tego; aplikacja bez serwera MCP nie potrzebuje mostu JSON; aplikacja bez synchronizacji między urządzeniami nie potrzebuje uzgadniacza. Get Bananas potrzebuje wszystkich trzech, ponieważ wszystkie trzy klasy wywołujących (człowiek przez iOS, Apple Intelligence przez App Intents na iOS, agent przez MCP z maszyny programisty Maca) dotykają tej samej listy zakupów.

Ścieżka aktualizacji: ostatni piszący wygrywa per wiersz

Produkcyjna polityka „kopia zapasowa wygrywa w czasie synchronizacji” jest tania i działa dla przypadku jeden użytkownik, jeden aktywny piszący jednocześnie. Ma trudności, gdy zarówno aplikacja iOS, jak i serwer MCP piszą do pliku JSON w bliskiej kolejności: który proces zapisał ostatnio, nadpisuje zmiany drugiego, w tym dla wierszy, które tak naprawdę nie były w konflikcie. Łagodzeniem dziś jest aplikacja iOS ponownie eksportująca po każdym zapisie SwiftData, co utrzymuje plik JSON z grubsza zgodny z najnowszym stanem wewnątrz aplikacji. Stan ustalony jest w porządku; rzeczywiście współbieżny przypadek może utracić pracę.

Najtańszą aktualizacją jest ostatni piszący wygrywa per wiersz oparty na kolumnie lastModified: Date?. Model ShoppingItem już ma pole dla bezpieczeństwa migracji (omówione w SwiftData’s Real Cost Is Schema Discipline), ale eksport JSON i serwer MCP obecnie go nie serializują ani nie honorują. Przepuszczenie lastModified przez eksport i przez applyExport zmieniłoby politykę scalania z „kopia zapasowa wygrywa” na „nowszy wiersz wygrywa”:

  • Obie strony mają wartość, jedna jest nowsza. Najnowsza wygrywa. Wiersz drugiej strony jest aktualizowany.
  • Obie strony mają wartość, są takie same. Rozstrzygnięcie po kluczu głównym lub po powierzchni (aplikacja iOS wygrywa remisy, aby faworyzować najnowszą interakcję użytkownika wewnątrz aplikacji).
  • Jedna strona ma wartość, druga nie. Strona z wartością wygrywa.
  • Żadna strona nie ma wartości. Oba wiersze są danymi z ery sprzed lastModified. Uzgadniacz stempluje Date() na następny raz.

Polityka jest tania, łatwa do uzasadnienia i błędna w mniej więcej 1% przypadków (równoczesne edycje różnych pól tego samego wiersza). Dla listy zakupów ten 1% nie ma znaczenia; dla edytora dokumentów absolutnie ma. Aplikacje wymagające silniejszych gwarancji nakładają scalanie na poziomie pól, CRDT lub transformacje operacyjne na tej bazie; Get Bananas jeszcze nie potrzebowało tej złożoności, dlatego LWW per wiersz jest na mapie drogowej, a bogatsze scalanie nie.

Trzy klasy wywołujących a podłoże

Mapowanie klas wywołujących z Three Surfaces na decyzje o podłożu:

Powierzchnia człowieka pisze przez SwiftData. Użytkownik stukający w pole wyboru w aplikacji iOS przesyła przez warstwę SwiftUI do funkcji domeny, która mutuje wiersz SwiftData, stempluje lastModified = Date() na modelu i zapisuje kontekst modelu. Eksport iCloud zapisuje bieżący stan do pliku JSON. Serwer MCP podnosi nowy stan przy następnym odczycie.

Powierzchnia Apple Intelligence pisze przez SwiftData. AppIntent wywołany przez Siri uruchamia się w procesie aplikacji iOS i sięga do tej samej funkcji domeny, której używa powierzchnia człowieka. Stan SwiftData mutuje, lastModified modelu się aktualizuje, a eksport JSON przechwytuje nowy stan.

Powierzchnia agenta pisze przez plik JSON. Wywołanie narzędzia MCP z sesji Claude Code na Macu mutuje plik JSON bezpośrednio (z blokowaniem plików obsługującym współbieżne zapisy z aplikacji iOS). Następnym razem, gdy aplikacja iOS się uruchomi lub zsynchronizuje, SyncManager.applyExport czyta plik, przechodzi przez elementy po UUID, aktualizuje wiersze, które istnieją po obu stronach, z wartości kopii zapasowej, dodaje wiersze, które kopia zapasowa ma, i usuwa wiersze, które kopia zapasowa pomija (z zabezpieczeniem pustej kopii zapasowej). Produkcyjna polityka to kopia zapasowa wygrywa w czasie synchronizacji; ścieżka aktualizacji dodaje lastModified do JSON, więc polityka może przejść do nowszy wiersz wygrywa.

Asymetria jest rzeczywista i zamierzona. Powierzchnie człowieka i Apple Intelligence obie działają wewnątrz procesu aplikacji iOS i używają SwiftData natywnie. Powierzchnia agenta działa poza procesem aplikacji iOS i używa pliku JSON, ponieważ to podłoże, do którego może sięgnąć. Uzgadniacz to to, co utrzymuje obie połowy razem.

Kiedy ten wzorzec jest złą odpowiedzią

Kilka przypadków, w których wzorzec mostu JSON jest błędny.

Dane o wysokiej częstotliwości zapisu. Edytor dokumentów na żywo z wieloma edycjami na sekundę nie może zapłacić kosztu serializowania całej kolekcji do pliku JSON przy każdym zapisie. Właściwą odpowiedzią są transformacje operacyjne lub CRDT wobec rzeczywistego zaplecza.

Wymagania silnej spójności. Księga transakcji finansowych nie może tolerować ostatni piszący wygrywa na pliku JSON. Właściwą odpowiedzią jest CloudKit (lub baza danych po stronie serwera) z jawną semantyką transakcyjną.

Współpraca wielu użytkowników, gdzie użytkownicy widzą wzajemne edycje w czasie rzeczywistym. Synchronizacja iCloud Drive jest ostatecznie spójna, a nie w czasie rzeczywistym. Użytkownik zamykający aplikację na jednym urządzeniu i otwierający ją na innym widzi stan, który się zsynchronizował; użytkownik widzący kursor innego użytkownika poruszający się po dokumencie nie. Właściwą odpowiedzią jest framework współpracy w czasie rzeczywistym (yjs, automerge lub niestandardowa warstwa WebSocket).

Przypadki, w których agent i użytkownik to różne tożsamości. Wzorzec Get Bananas zakłada, że agent (wywołujący MCP) i ludzki użytkownik (użytkownik aplikacji iOS) to ta sama osoba, po prostu działająca przez procesy. Jeśli agent działa w imieniu innej tożsamości (lista współdzielona, administrator, automatyczny bot), plik JSON w iCloud Drive użytkownika jest złym podłożem; wymagana jest trwałość wielu użytkowników z jawnym uwierzytelnianiem.

Wzorzec pasuje do przypadku jednego użytkownika, ostatecznie spójnego, międzyprocesowego. Większość aplikacji z integracjami MCP to dokładnie ten przypadek; niektóre nie.

Co zbudowałbym inaczej

Trzy wzorce, które aplikacje klastra albo dostarczyły, albo żałują, że nie dostarczyły.

Niech serializacja JSON będzie jawna, a nie domyślna. Pierwsza wersja Get Bananas eksportowała każdy zapis SwiftData do JSON w haku zapisu. Druga wersja uczyniła eksport jawnym krokiem, który aplikacja wywołuje, gdy stan się ustabilizował. Zmiana zmniejszyła zbędne zapisy i wyjaśniła, kiedy stan międzyprocesowy został opublikowany. Domyślny hak zapisu przy każdej mutacji wytwarza zbyt wiele I/O dla jakiejkolwiek nietrywialnej kolekcji.

Wersjonowanie schematu pliku JSON. Plik JSON ma własny schemat niezależny od VersionedSchema SwiftData. Gdy schemat SwiftData się zmienia (powiedzmy, dodanie pola), serializacja JSON musi za tym podążać. Tanim rozwiązaniem jest umieszczenie pola schemaVersion: Int na górze JSON; uzgadniacz odczytuje je i stosuje właściwą interpretację. Bez wersjonowania aplikacja iOS v2 czytająca plik JSON v1 zapisany przez stary serwer MCP napotyka ciche uszkodzenie danych.

Blokowanie pliku JSON, nie zakładanie koordynacji. Aplikacja iOS i serwer MCP obie piszą do pliku JSON. Bez NSFileCoordinator (wewnątrz procesu, po stronie iOS) i blokady plikowej (poza procesem, na maszynie programisty) współbieżne zapisy mogą wytworzyć uszkodzony plik. Serwer MCP Get Bananas używa blokady plikowej w stylu flock na JSON; aplikacja iOS używa NSFileCoordinator dla swoich zapisów; plik rzadko jest sporny w praktyce, ale pas bezpieczeństwa jest tani.

Co wzorzec oznacza dla aplikacji wysyłających na iOS 26+

Trzy wnioski.

  1. Wybierz jedno źródło prawdy na domenę. Pozostałe podłoża buforują, lustrzanie odbijają lub schodzą z drogi. SwiftData dla zapytań wewnątrz procesu, JSON w iCloud Drive dla mostów międzyprocesowych, NSUbiquitousKeyValueStore dla małego stanu między urządzeniami. Rozwiązywanie konfliktów jest dalszą konsekwencją wyboru.

  2. lastModified plus ostatni piszący wygrywa to tani przypadek bazowy. Większość aplikacji nie potrzebuje silniejszych gwarancji. Ten 1% przypadków, które potrzebują scalania na poziomie pól lub CRDT, jest drogi do dodania; nie należy płacić kosztu, dopóki kształt danych tego nie wymaga.

  3. Uzgadniacz to element nośny. Gdy SwiftData i plik JSON się nie zgadzają, uzgadniacz decyduje. Uzgadniacz uruchamia się przy starcie aplikacji, po zapisach międzyprocesowych i po zdarzeniach synchronizacji iCloud. Reguły są proste; dyscyplina polega na faktycznym jego uruchamianiu.

Pełny klaster Apple Ecosystem: typowane App Intents dla powierzchni Apple Intelligence; serwery MCP dla powierzchni agenta; pytanie o routing między nimi; Foundation Models dla funkcji LLM na urządzeniu wewnątrz aplikacji; rozróżnienie runtime vs tooling LLM; synteza trzech powierzchni aplikacji iOS; Live Activities dla maszyny stanów ekranu blokady iOS; kontrakt runtime watchOS na Apple Watch; wewnętrzne mechanizmy SwiftUI dla podłoża powierzchni człowieka; model mentalny przestrzenny RealityKit dla scen visionOS; dyscyplina schematu SwiftData dla trwałości; wzorce Liquid Glass dla warstwy wizualnej; wysyłka wieloplatformowa dla zasięgu międzyurządzeniowego. Centrum znajduje się w serii Apple Ecosystem. Dla szerszego kontekstu iOS-z-agentami-AI należy zobaczyć przewodnik iOS Agent Development.

FAQ

Dlaczego nie używać CloudKit do synchronizacji międzyprocesowej?

CloudKit zapewnia silną synchronizację międzyurządzeniową z rekordami świadomymi konfliktów, a CloudKit JS / CloudKit Web Services Apple pozwalają stosom innym niż Apple sięgnąć do prywatnej bazy danych CloudKit. Ograniczeniem jest koszt integracji: serwer MCP w Node.js używający CloudKit musi obsłużyć uwierzytelnianie CloudKit (klucze server-to-server lub tokeny na poziomie użytkownika), deklaracje schematów i podpisane żądania. iCloud Drive plus plik JSON to zwykłe wejście/wyjście plikowe, które jest uniwersalnym tłumaczem. CloudKit jest właściwym wyborem, gdy zespół jest gotów zapłacić koszt integracji i chce silniejszej synchronizacji oraz semantyki konfliktów CloudKit; most JSON jest właściwym wyborem, gdy „otwórz plik” wystarcza dla kształtu danych.

Jak obsługiwać konflikty, gdy dwóch wywołujących pisze w tym samym czasie?

Produkcyjna polityka Get Bananas to „kopia zapasowa wygrywa w czasie synchronizacji”: SyncManager.applyExport przechodzi przez elementy po UUID i nadpisuje lokalne wiersze z kopii zapasowej, z zabezpieczeniem przed pustą kopią zapasową wymazującą dobre dane lokalne. Ścieżką aktualizacji jest ostatni piszący wygrywa per wiersz oparty na lastModified, który model już niesie, ale który nie jest jeszcze serializowany przez most JSON. Dodanie tego rozwiązałoby ~99% konfliktów, w których jedna strona naprawdę jest nowsza; pozostały 1% (równoczesne edycje różnych pól tego samego wiersza) jest na tyle rzadki, że aplikacje na razie pomijają scalanie na poziomie pól lub CRDT. Aplikacje z silniejszymi wymaganiami spójności nakładają bogatsze scalanie na wierzch.

Gdzie pasuje SwiftData, jeśli iCloud Drive jest źródłem prawdy dla stanu międzyprocesowego?

SwiftData jest źródłem prawdy dla zapytań wewnątrz procesu. Aplikacja iOS czyta SwiftData dla każdego renderowania interfejsu, każdego @Query, każdego wyszukiwania. SwiftData jest szybkie, typowane schematem i zintegrowane z systemem obserwacji SwiftUI. Gdy aplikacja iOS pisze, zmiana idzie najpierw do SwiftData, a następnie jest eksportowana do pliku JSON. Plik JSON jest źródłem prawdy dla odczytów międzyprocesowych (widok serwera MCP); SwiftData jest źródłem prawdy dla odczytów wewnątrz procesu (widok interfejsu iOS). Pozostają zgodne dzięki uzgadniaczowi.

A co z NSUbiquitousKeyValueStore dla samej listy zakupów?

NSUbiquitousKeyValueStore jest ograniczone do 1MB łącznie na aplikację i 1MB na wartość z dławionymi zapisami i serializuje na słowniku 1024 kluczy. Lista zakupów z setkami elementów plus historia może zmieścić się pod względem liczby bajtów, ale zapisywanie zmian per element przez dławienie to zły kształt; masowe aktualizacje kolekcji konkurują o budżet szybkości ze wszystkim innym, co aplikacja przechowuje. Właściwym podłożem dla kolekcji jest albo SwiftData (wewnątrz procesu), albo iCloud Drive (międzyprocesowe). Należy zarezerwować NSUbiquitousKeyValueStore dla małego stanu klucz-wartość o niskiej częstotliwości: ustawienia, ostatnio wybrana karta, licznik, nadpisanie flagi funkcji.

Jak rozpoznać, które podłoże wybrać dla nowej domeny w mojej aplikacji?

Trzy pytania w kolejności. Pierwsze: czy cokolwiek poza procesem aplikacji iOS musi czytać lub zapisywać tę domenę? Jeśli tak, potrzebny jest iCloud Drive (oparty na plikach, zwykłe wejście/wyjście plikowe) lub CloudKit (przez frameworki Apple lub CloudKit Web Services ze stosów innych niż Apple) lub serwer pod własną kontrolą. Jeśli nie, SwiftData jest domyślne. Drugie: czy to musi się synchronizować między urządzeniami użytkownika? Jeśli tak, podłoże musi to wspierać (iCloud Drive tak, SwiftData nie, chyba że sparowane z synchronizacją iCloud). Trzecie: jak duży jest ładunek i jak często się zmienia? Mały + niska częstotliwość mieszka w NSUbiquitousKeyValueStore; wszystko inne potrzebuje rzeczywistej warstwy trwałości.

Bibliografia


  1. Analiza autora w Two Agent Ecosystems, One Shopping List, 29 kwietnia 2026, oraz warstwa synchronizacji JSON iCloud Drive projektu Get Bananas w Banana List/iCloudBackupManager.swift. Architektura łączy SwiftData z plikiem JSON w kontenerze iCloud Drive użytkownika, który zewnętrzny serwer MCP czyta i zapisuje. 

  2. Apple Developer, “SwiftData” i “Adding and editing persistent data in your app”. Makro @Model, ograniczenia @Attribute i relacja z NSManagedObjectModel Core Data. Analiza autora w SwiftData’s Real Cost Is Schema Discipline obejmuje VersionedSchema i MigrationPlan dla bezpiecznej ewolucji schematu. 

  3. Apple Developer, “Synchronizing documents in the iCloud environment”. Synchronizacja międzyurządzeniowa oparta na plikach, rozwiązywanie konfliktów przez NSFileVersion i API NSFileCoordinator dla bezpiecznych zapisów wewnątrz procesu wobec współdzielonych plików. 

  4. Apple Developer, “NSUbiquitousKeyValueStore”. Pamięć klucz-wartość między urządzeniami. Aktualne limity Apple: 1MB łącznie na aplikację, 1MB na wartość, 1024 klucze, 128 znaków UTF-16 na klucz, dławiona szybkość zapisu. Analiza autora w Five Apple Platforms, Three Shared Files obejmuje wzorzec timera między urządzeniami, który dostarcza Return przy użyciu tego API. 

Powiązane artykuły

Serwer MCP obok aplikacji iOS: dwa ekosystemy agentów, jedna lista

Get Bananas działa na iOS, macOS, watchOS i visionOS. Żyje również wewnątrz Claude Desktop jako serwer MCP. Most: iCloud…

15 min czytania

App Intents kontra MCP: Pytanie o routing

Dwa protokoły, jedna aplikacja. App Intents udostępniają aplikację Apple Intelligence. MCP udostępnia tę samą domenę Cla…

12 min czytania

Pański agent ma pośrednika, którego Pan nie zweryfikował

Badacze przetestowali 28 routerów LLM API. 17 z nich dotknęło poświadczeń-pułapek AWS. Jeden opróżnił ETH z klucza prywa…

11 min czytania