단일 진실 공급원: SwiftData, MCP, iCloud
Get Bananas에는 같은 쇼핑 목록에 쓸 수 있는 세 가지 호출자가 있습니다. iOS 앱에서 행을 탭하는 사람. Siri 요청을 AppIntent를 통해 라우팅하는 Apple Intelligence. stdio를 통해 MCP 도구를 호출하는 Claude Code 세션. 사용자의 머릿속에서 이 목록은 하나의 정형화된 것이지만, 문제는 그것이 어디에 저장되어 있고 호출자들이 충돌할 때 누가 이기느냐입니다.
이 합성 글은 iOS 앱의 세 가지 표면을 명명했습니다. 사람, Apple Intelligence, 에이전트입니다. 각 표면은 동일한 도메인 상태를 읽고 써야 합니다. 그 요구가 너무 많은 앱에서 출시되는 아키텍처적 실수를 만들어 냅니다. 각 표면이 자신의 저장소를 가지게 되고, 표면들이 어긋나며, 사용자는 마지막으로 어느 것을 만졌는지에 따라 자신의 목록의 세 가지 다른 버전을 보게 됩니다. 살아남는 패턴은 단일 진실 공급원에 표면들과 기반 사이의 명시적인 동기화 경로를 갖는 것입니다.
이 글은 기반 선택지, 각각이 강요하는 충돌 해결 규칙, 그리고 세 호출자 클래스 모두가 쓸 수 있을 때 유지되는 아키텍처를 명명합니다. 워크스루는 Get Bananas의 실제 구조를 사용합니다. 인앱 상태를 위한 SwiftData, 프로세스 간 동기화를 위한 iCloud Drive JSON 파일, iOS 샌드박스 외부에서 같은 파일을 읽고 쓰는 MCP 서버입니다.1
TL;DR
- 세 가지 기반이 결합됩니다. SwiftData (인프로세스, 빠름, 스키마 타입화), iCloud Drive (프로세스 간, 파일 기반, 동기화 가능),
NSUbiquitousKeyValueStore(장치 간, 키-값, 작은 페이로드 전용). - 도메인별로 어떤 기반이 진실 공급원인지 선택하세요. 충돌 해결은 별도의 문제가 아니라 그 선택의 강제된 결과입니다.
- 세 가지 표면(사람, Apple Intelligence, 에이전트)은 각각 도메인 계층을 통해 기반과 상호작용합니다. 충돌 해결 정책이 사는 곳이 도메인 계층입니다.
- 모든 가변 엔티티의
lastModified타임스탬프는 저렴한 충돌 해결 프리미티브이며, “마지막에 쓴 사람이 이긴다”는 저렴한 정책입니다. 더 강력한 보장이 필요한 앱은 명시적인 병합 로직으로 그 비용을 지불합니다. - 앱 프로세스 외부의 MCP 서버는 SwiftData를 읽을 수 없습니다. 다리는 직렬화 계층(iCloud Drive의 JSON 파일, App Group 컨테이너 등)이며, 인앱 SwiftData 리더와 프로세스 외부 MCP 서버 모두가 이와 통신할 수 있습니다.
세 가지 기반
Apple은 출시된 iOS 앱에 프로세스 간 및 장치 간 패턴에서 나타나는 세 가지 네이티브 영속화 기반을 제공합니다. 각각은 특정한 형태를 가지며, 계획 없이 섞으면 어긋남 문제를 만들어 냅니다.
SwiftData. Core Data로 뒷받침되는 인프로세스 영속 저장소입니다.2 빠르고, 스키마 타입화되며, @Query를 통해 쿼리 가능하고, SwiftUI의 관찰 시스템과 통합됩니다. 저장소는 앱 프로세스가 소유합니다. 앱 확장(위젯, 인텐트, 공유 확장)은 명시적인 구성을 통해 App Group을 거쳐 SwiftData 컨테이너를 공유할 수 있지만, 앱의 서명 컨텍스트 외부의 개발자 머신에서 실행되는 임의의 외부 MCP 프로세스는 SwiftData 컨테이너에 안전하게 도달할 수 없습니다. 행은 PersistentIdentifier(인프로세스) 또는 @Attribute(.unique) 자연 키(프로세스 간, 장치 간)를 통해 주소 지정 가능합니다. 마이그레이션은 VersionedSchema와 MigrationPlan을 통해 선언적으로 이루어집니다(SwiftData의 진짜 비용은 스키마 규율입니다에서 다룸).
iCloud Drive. 사용자의 iCloud 컨테이너에서 FileManager URL을 통해 노출되는 파일 기반 장치 간 동기화입니다.3 파일은 사용자가 로그인한 모든 장치에 나타납니다. 충돌 해결은 파일 수준에서 이루어집니다. iCloud는 NSFileVersion을 사용하여 동시 편집을 추적하고, 앱은 충돌 로그를 통해 무엇을 유지할지 결정합니다. iCloud Drive의 파일은 iOS 앱 프로세스 외부에서도 주소 지정이 가능합니다. Mac의 MCP 서버는 iOS 앱이 읽는 것과 같은 JSON 파일을 열 수 있습니다. 이 기반이 Get Bananas의 MCP 통합을 작동하게 합니다.
NSUbiquitousKeyValueStore. 장치 간 키-값 저장소입니다. Apple의 현재 공개 한도는 앱당 총 1MB, 값당 1MB, 1024개 키, 키당 128 UTF-16 문자, 그리고 제한된 쓰기 속도입니다.4 충돌 해결은 내장되어 있습니다. 시스템은 쓰기를 직렬화하고 변경 시 모든 장치에 알립니다. 작고, 저빈도 상태(설정, 마지막으로 선택한 탭, 정수 카운터)에는 적합하며, 고빈도 쓰기 데이터나 제한이 병목이 되는 워크로드에는 부적합합니다. Return은 이를 장치 간 타이머 상태에 사용합니다(사용자가 iPhone에서 타이머를 시작하고 Apple Watch에서 이를 봅니다). 다섯 개의 Apple 플랫폼, 세 개의 공유 파일에서 다룹니다.
네 번째 기반인 CloudKit은 명백해 보이는 선택지이지만 클러스터의 앱들은 프로세스 간 MCP 통합을 위해 명시적으로 거부했습니다. CloudKit은 충돌 인식 레코드를 통한 강력한 장치 간 동기화를 제공하며, Apple은 비-Apple 환경이 공개 및 비공개 CloudKit 데이터베이스와 통신할 수 있도록 CloudKit JS와 CloudKit Web Services를 출시합니다. 솔직한 트레이드오프는 불가능성이 아니라 통합 비용입니다. 비공개 CloudKit 데이터베이스에 도달하는 Node.js MCP 서버는 CloudKit Web Services 인증, 스키마 정의, 요청 서명을 연결해야 하며, 이는 “JSON 파일을 연다”에 비해 상당한 엔지니어링 작업입니다. Get Bananas는 iCloud Drive에 JSON 파일을 더하는 방식을 선택했습니다. MCP 서버가 iOS 앱이 보는 것과 같은 데이터를 읽고 써야 하는 Node.js 프로세스이고, 일반 파일 I/O가 가장 저항이 적은 경로이기 때문입니다.1
결정: 어떤 기반이 진실을 보유할 것인가
문제는 어떤 기반을 사용할 것인가가 아닙니다. 문제는 어떤 기반이 어떤 도메인의 진실 공급원인가입니다. 다른 기반들은 캐시하거나, 미러링하거나, 비켜섭니다.
클러스터 앱의 프로덕션에서 살아남는 결정 매트릭스는 다음과 같습니다.
| 도메인 형태 | 진실 공급원 | 이유 |
|---|---|---|
| 설정, 환경설정, 단순 플래그 | NSUbiquitousKeyValueStore |
장치 간 동기화가 자동이며, 충돌은 직렬화되고, 작은 페이로드가 적합 |
| 장치별 일시적 상태 | UserDefaults (동기화 없음) |
장치 로컬, 다른 장치의 새 설치에서 살아남으면 안 됨 |
| 인프로세스 쿼리 가능 컬렉션 | SwiftData | 빠른 @Query, SwiftUI 관찰, 스키마 타입화, 인프로세스 전용 |
| 외부 프로세스에 도달해야 하는 인프로세스 컬렉션 | iCloud Drive JSON 파일 (디스크로 내보내기) | iOS SwiftData 리더와 외부 MCP 서버 모두 파일을 읽을 수 있음 |
| 사용자별 대용량 콘텐츠 (사진, 오디오, 문서) | iCloud Drive (파일 단위) | 사용자의 iCloud가 자연스러운 저장소, CloudKit이 더 풍부한 동기화를 위해 그 위에 계층화될 수 있음 |
| 장치 간 세션 수준 상태 (iPhone에서 실행 중인 타이머, Watch에서 보기) | NSUbiquitousKeyValueStore |
크기 한도에 적합, 장치 간 푸시 시맨틱이 필요 |
이 결정이 충돌 해결 정책을 형성합니다. 로컬 앱과 외부 MCP 사이의 다리에서 SwiftData는 프로세스 간 본질적인 충돌 해결을 가지지 않습니다. 두 호출자가 같은 행에 쓰면, 마지막 try context.save()가 이깁니다. CloudKit으로 뒷받침되고 영속 기록을 사용하는 SwiftData는 더 풍부한 장치 간 시맨틱을 가질 수 있지만, 그 표면은 iOS 측이며 외부 Node.js MCP 사례에는 도움이 되지 않습니다. iCloud Drive는 충돌을 NSFileVersion 항목으로 표면화합니다. 앱이 그것들을 살펴보고 승자를 골라야 합니다. NSUbiquitousKeyValueStore는 값 수준에서 내장된 충돌 해결을 가지고 있습니다.
Get Bananas 아키텍처
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.
이 아키텍처에는 세 가지 규칙이 있습니다.
SwiftData는 인프로세스 쿼리의 진실 공급원입니다. iOS 앱은 모든 UI 렌더링, 모든 @Query 기반 목록, 모든 검색에 대해 SwiftData에서 읽습니다. 쓰기는 SwiftData를 먼저 통과합니다. 모델 컨텍스트가 저장하고, SwiftUI가 다시 렌더링합니다.
JSON 파일은 프로세스 간 상태의 진실 공급원입니다. iOS 앱이 SwiftData에 저장할 때마다, iCloud 백업 관리자가 현재 상태를 사용자의 iCloud Drive 컨테이너에 있는 JSON 파일로 내보냅니다. MCP 서버가 쓸 때마다, 같은 JSON 파일에 씁니다. 파일이 다리입니다.
iOS 앱 시작 시와 모든 프로세스 간 쓰기 후에 동기화 패스가 실행됩니다. 오늘날 프로덕션의 동기화 로직(SyncManager.applyExport)은 각 패스에서 JSON 백업을 권위 있는 것으로 취급합니다. JSON 파일을 읽고, 각 행을 UUID로 SwiftData와 일치시키고, 기존 행을 백업의 값으로 덮어쓰고, 백업에는 있지만 SwiftData에는 없는 행을 추가하고, SwiftData에는 있지만 백업에는 없는 행을 삭제합니다(빈 백업 파일이 로컬 데이터베이스를 지우는 것에 대한 손상 방지 장치 포함). 정책은 동기화 시점에 백업이 이긴다이며, 타임스탬프에 의한 행별 마지막에 쓴 사람이 이기는 것이 아닙니다. iOS 앱이 모든 저장 후에 다시 내보내는 것과 결합되어, 정상 상태 수렴은 실무적으로 빠릅니다. 어느 프로세스가 가장 최근에 썼든 다음 동기화가 읽는 JSON를 생산했습니다.
이 아키텍처는 프로세스 간 도달을 위해 복잡성을 거래합니다. 순수 SwiftData 앱은 이 중 어느 것도 필요로 하지 않습니다. MCP 서버가 없는 앱은 JSON 다리가 필요하지 않습니다. 장치 간 동기화가 없는 앱은 조정자가 필요하지 않습니다. Get Bananas는 세 가지 호출자 클래스 모두(iOS를 통한 사람, iOS의 App Intents를 통한 Apple Intelligence, Mac 개발자의 머신에서 MCP를 통한 에이전트)가 같은 쇼핑 목록을 만지기 때문에 세 가지 모두가 필요합니다.
업그레이드 경로: 행별 마지막에 쓴 사람이 이긴다
“동기화 시점에 백업이 이긴다”는 출시 정책은 저렴하며 단일 사용자, 한 번에 단일 활성 작성자 사례에 작동합니다. iOS 앱과 MCP 서버 모두가 짧은 시간 내에 JSON 파일에 쓸 때 어려움을 겪습니다. 가장 최근에 쓴 프로세스가 실제로 충돌하지 않은 행을 포함하여 다른 쪽의 변경 사항을 덮어씁니다. 오늘날의 완화책은 모든 SwiftData 저장 후에 iOS 앱이 다시 내보내는 것이며, 이는 JSON 파일을 가장 최근의 인앱 상태와 대략 일치시킵니다. 정상 상태는 괜찮습니다. 진정으로 동시적인 사례는 작업을 잃을 수 있습니다.
가장 저렴한 업그레이드는 lastModified: Date? 컬럼에 키가 있는 행별 마지막에 쓴 사람이 이기는 것입니다. ShoppingItem 모델은 마이그레이션 안전성을 위해 이미 이 필드를 가지고 있지만(SwiftData의 진짜 비용은 스키마 규율입니다에서 다룸), JSON 내보내기와 MCP 서버는 현재 이를 직렬화하거나 존중하지 않습니다. lastModified를 내보내기와 applyExport를 통해 연결하면 병합 정책이 “백업이 이긴다”에서 “더 최근 행이 이긴다”로 바뀝니다.
- 양쪽 모두 값을 가지고 있으며, 하나가 더 최근입니다. 가장 최근이 이깁니다. 다른 쪽의 행이 업데이트됩니다.
- 양쪽 모두 값을 가지고 있으며, 동률입니다. 기본 키로 동률 결정, 또는 표면별로(iOS 앱이 동률에서 이겨 사용자의 가장 최근 인앱 상호작용을 우선시).
- 한쪽은 값을 가지고 있고, 다른 쪽은 가지지 않습니다. 값을 가진 쪽이 이깁니다.
- 양쪽 모두 값을 가지지 않습니다. 두 행 모두
lastModified시대 이전 데이터입니다. 조정자는 다음 번을 위해Date()로 스탬프를 찍습니다.
이 정책은 저렴하고, 추론하기 쉬우며, 약 1%의 사례(같은 행의 다른 필드에 대한 동시 편집)에서 틀립니다. 쇼핑 목록의 경우 1%는 중요하지 않습니다. 문서 편집기의 경우 절대적으로 중요합니다. 더 강력한 보장이 필요한 앱은 이 기반 위에 필드 수준 병합, CRDT 또는 운영 변환을 계층화합니다. Get Bananas는 아직 그런 복잡성을 필요로 하지 않았으며, 이것이 행별 LWW가 로드맵에 있고 더 풍부한 병합이 그렇지 않은 이유입니다.
세 가지 호출자 클래스와 기반
세 가지 표면의 호출자 클래스를 기반 결정에 매핑하면 다음과 같습니다.
사람 표면은 SwiftData를 통해 씁니다. iOS 앱에서 체크박스를 탭하는 사용자는 SwiftUI 계층을 통해 도메인 함수로 발사되며, 이 함수는 SwiftData 행을 변경하고, 모델에 lastModified = Date()를 스탬프 찍고, 모델 컨텍스트를 저장합니다. iCloud 내보내기는 현재 상태를 JSON 파일에 씁니다. MCP 서버는 다음 읽기에서 새 상태를 가져갑니다.
Apple Intelligence 표면은 SwiftData를 통해 씁니다. Siri를 통해 호출된 AppIntent는 iOS 앱 프로세스에서 실행되며 사람 표면이 사용하는 동일한 도메인 함수에 도달합니다. SwiftData 상태가 변경되고, 모델의 lastModified가 업데이트되며, JSON 내보내기가 새 상태를 캡처합니다.
에이전트 표면은 JSON 파일을 통해 씁니다. Mac의 Claude Code 세션에서 MCP 도구 호출은 JSON 파일을 직접 변경합니다(iOS 앱의 동시 쓰기를 처리하기 위한 파일 잠금 포함). 다음에 iOS 앱이 시작되거나 동기화될 때, SyncManager.applyExport가 파일을 읽고, UUID로 항목을 살펴보고, 양쪽에 존재하는 행을 백업의 값에서 업데이트하고, 백업에 있는 행을 추가하고, 백업이 생략한 행을 삭제합니다(빈 백업 보호 포함). 출시 정책은 동기화 시점에 백업이 이긴다입니다. 업그레이드 경로는 JSON에 lastModified를 추가하여 정책이 더 최근 행이 이긴다로 바뀔 수 있게 합니다.
이 비대칭은 실재하며 의도적입니다. 사람과 Apple Intelligence 표면은 모두 iOS 앱 프로세스 내에서 실행되며 SwiftData를 네이티브로 사용합니다. 에이전트 표면은 iOS 앱 프로세스 외부에서 실행되며 그것이 도달할 수 있는 기반이기 때문에 JSON 파일을 사용합니다. 조정자가 두 절반을 함께 묶는 역할을 합니다.
이 패턴이 잘못된 답인 경우
JSON-다리 패턴이 잘못된 몇 가지 사례입니다.
고빈도 쓰기 데이터. 초당 많은 편집이 있는 라이브 문서 편집기는 매번 쓰기마다 전체 컬렉션을 JSON 파일로 직렬화하는 비용을 감당할 수 없습니다. 올바른 답은 실제 백엔드에 대한 운영 변환 또는 CRDT입니다.
강력한 일관성 요구사항. 금융 거래 원장은 JSON 파일에서 마지막에 쓴 사람이 이기는 것을 용인할 수 없습니다. 올바른 답은 명시적인 트랜잭션 시맨틱을 갖춘 CloudKit(또는 서버 측 데이터베이스)입니다.
여러 사용자가 서로의 편집을 실시간으로 보는 다중 사용자 협업. iCloud Drive 동기화는 최종 일관성이며 실시간이 아닙니다. 한 장치에서 앱을 닫고 다른 장치에서 여는 사용자는 동기화된 상태를 봅니다. 다른 사용자의 커서가 문서를 가로질러 이동하는 것을 보는 사용자는 그렇지 않습니다. 올바른 답은 실시간 협업 프레임워크(yjs, automerge 또는 사용자 정의 WebSocket 계층)입니다.
에이전트와 사용자가 다른 정체성인 경우. Get Bananas 패턴은 에이전트(MCP 호출자)와 인간 사용자(iOS 앱 사용자)가 단지 프로세스를 가로질러 작동하는 동일한 사람이라고 가정합니다. 에이전트가 다른 정체성(공유 목록, 관리자, 자동화된 봇)을 대신해서 행동한다면, 사용자의 iCloud Drive에 있는 JSON 파일은 잘못된 기반입니다. 명시적 인증을 갖춘 다중 사용자 영속화가 필요합니다.
이 패턴은 단일 사용자, 최종 일관성, 프로세스 간 사례에 적합합니다. MCP 통합이 있는 대부분의 앱은 정확히 그 사례입니다. 일부는 그렇지 않습니다.
다르게 만들 것들
클러스터의 앱들이 출시했거나 출시했더라면 좋았을 세 가지 패턴입니다.
JSON 직렬화를 암묵적이 아니라 명시적으로 만드세요. Get Bananas의 첫 번째 버전은 모든 SwiftData 쓰기를 저장 훅에서 JSON로 내보냈습니다. 두 번째 버전은 내보내기를 상태가 안정화되었을 때 앱이 호출하는 명시적인 단계로 만들었습니다. 이 변경은 중복 쓰기를 줄이고 프로세스 간 상태가 언제 게시되었는지를 명확하게 만들었습니다. 모든 변경 시 암묵적 저장 훅은 사소하지 않은 컬렉션에 너무 많은 I/O를 생성합니다.
JSON 파일의 스키마를 버전화하세요. JSON 파일은 SwiftData의 VersionedSchema와 독립적인 자체 스키마를 가지고 있습니다. SwiftData 스키마가 변경될 때(예를 들어, 필드를 추가할 때) JSON 직렬화도 따라야 합니다. 저렴한 해결책은 JSON의 맨 위에 schemaVersion: Int 필드를 두는 것입니다. 조정자가 이를 읽고 올바른 해석을 적용합니다. 버전 관리 없이는, 이전 MCP 서버가 작성한 v1 JSON 파일을 읽는 v2 iOS 앱은 조용한 데이터 손상에 부딪힙니다.
JSON를 파일 잠금하고, 조정을 가정하지 마세요. iOS 앱과 MCP 서버 모두가 JSON 파일에 씁니다. NSFileCoordinator(인프로세스, iOS 측)와 파일 잠금(프로세스 외부, 개발자의 머신에) 없이는, 동시 쓰기가 손상된 파일을 만들 수 있습니다. Get Bananas의 MCP 서버는 JSON에 flock 스타일 파일 잠금을 사용합니다. iOS 앱은 자신의 쓰기에 NSFileCoordinator를 사용합니다. 파일은 실무적으로 거의 경합되지 않지만 안전벨트는 저렴합니다.
이 패턴이 iOS 26+에 출시하는 앱에 의미하는 것
세 가지 시사점입니다.
-
도메인당 하나의 진실 공급원을 선택하세요. 다른 기반들은 캐시하거나, 미러링하거나, 비켜섭니다. 인프로세스 쿼리에는 SwiftData, 프로세스 간 다리에는 iCloud Drive JSON, 작은 장치 간 상태에는
NSUbiquitousKeyValueStore. 충돌 해결은 그 선택의 다운스트림 결과입니다. -
lastModified더하기 마지막에 쓴 사람이 이기는 것이 저렴한 기본 사례입니다. 대부분의 앱은 더 강력한 보장을 필요로 하지 않습니다. 필드 수준 병합 또는 CRDT가 필요한 1%의 사례는 추가하기 비쌉니다. 데이터 형태가 요구할 때까지 비용을 지불하지 마세요. -
조정자가 핵심 부품입니다. SwiftData와 JSON 파일이 일치하지 않을 때 조정자가 결정합니다. 조정자는 앱 시작, 프로세스 간 쓰기 후, iCloud 동기화 이벤트 후에 실행됩니다. 규칙은 단순합니다. 규율은 실제로 그것을 실행하는 것입니다.
전체 Apple Ecosystem 클러스터: Apple Intelligence 표면을 위한 타입화된 App Intents, 에이전트 표면을 위한 MCP 서버, 그들 사이의 라우팅 질문, 인앱 온디바이스 LLM 기능을 위한 Foundation Models, 런타임 대 도구 LLM 구분, iOS 앱의 세 가지 표면 합성, iOS 잠금 화면 상태 머신을 위한 Live Activities, Apple Watch에서 watchOS 런타임 계약, 사람 표면의 기반을 위한 SwiftUI 내부, visionOS 장면을 위한 RealityKit의 공간 멘탈 모델, 영속화를 위한 SwiftData 스키마 규율, 시각 계층을 위한 Liquid Glass 패턴, 장치 간 도달을 위한 멀티 플랫폼 출시. 허브는 Apple Ecosystem 시리즈에 있습니다. 더 광범위한 iOS와 AI 에이전트 컨텍스트를 보려면 iOS Agent Development 가이드를 참조하세요.
FAQ
왜 프로세스 간 동기화에 CloudKit을 사용하지 않나요?
CloudKit은 충돌 인식 레코드를 통한 강력한 장치 간 동기화를 제공하며, Apple의 CloudKit JS / CloudKit Web Services는 비-Apple 스택이 비공개 CloudKit 데이터베이스에 도달할 수 있게 합니다. 제약은 통합 비용입니다. CloudKit을 사용하는 Node.js MCP 서버는 CloudKit의 인증(서버 간 키 또는 사용자 수준 토큰), 스키마 선언, 서명된 요청을 처리해야 합니다. iCloud Drive에 JSON 파일을 더하는 것은 일반 파일 I/O이며, 이는 보편적인 번역기입니다. CloudKit은 팀이 통합 비용을 지불할 의향이 있고 CloudKit의 더 강력한 동기화와 충돌 시맨틱을 원할 때 올바른 선택입니다. JSON 다리는 데이터 형태에 “파일을 연다”가 충분할 때 올바른 선택입니다.
두 호출자가 동시에 쓸 때 충돌을 어떻게 처리하나요?
Get Bananas의 출시 정책은 “동기화 시점에 백업이 이긴다”입니다. SyncManager.applyExport는 UUID로 항목을 살펴보고 백업에서 로컬 행을 덮어쓰며, 빈 백업이 좋은 로컬 데이터를 지우는 것에 대한 보호 장치를 갖추고 있습니다. 업그레이드 경로는 lastModified에 키가 있는 행별 마지막에 쓴 사람이 이기는 것이며, 모델은 이미 이 필드를 가지고 있지만 아직 JSON 다리를 통해 직렬화되지는 않습니다. 이를 추가하면 한쪽이 진정으로 더 최근인 충돌의 약 99%가 해결됩니다. 나머지 1%(같은 행의 다른 필드에 대한 동시 편집)는 지금까지의 앱이 필드 수준 병합이나 CRDT를 건너뛸 만큼 드물게 발생합니다. 더 강력한 일관성 요구사항이 있는 앱은 그 위에 더 풍부한 병합을 계층화합니다.
iCloud Drive가 프로세스 간 상태의 진실 공급원이라면 SwiftData는 어디에 적합한가요?
SwiftData는 인프로세스 쿼리의 진실 공급원입니다. iOS 앱은 모든 UI 렌더링, 모든 @Query, 모든 검색에 대해 SwiftData를 읽습니다. SwiftData는 빠르고, 스키마 타입화되며, SwiftUI의 관찰 시스템과 통합됩니다. iOS 앱이 쓸 때, 변경 사항은 먼저 SwiftData로 가고, 그 다음 JSON 파일로 내보내집니다. JSON 파일은 프로세스 간 읽기(MCP 서버의 뷰)의 진실 공급원입니다. SwiftData는 인프로세스 읽기(iOS UI의 뷰)의 진실 공급원입니다. 그들은 조정자를 통해 정렬을 유지합니다.
쇼핑 목록 자체에 NSUbiquitousKeyValueStore는 어떨까요?
NSUbiquitousKeyValueStore는 앱당 총 1MB와 값당 1MB로 제한되며, 제한된 쓰기와 1024개 키 사전에서 직렬화됩니다. 수백 개의 항목과 이력이 있는 쇼핑 목록은 바이트 수로 적합할 수 있지만, 제한을 통해 항목별 변경 사항을 쓰는 것은 잘못된 형태입니다. 대량 컬렉션 업데이트는 앱이 저장하는 다른 모든 것에 대해 속도 예산을 두고 경쟁합니다. 컬렉션을 위한 올바른 기반은 SwiftData(인프로세스) 또는 iCloud Drive(프로세스 간) 중 하나입니다. NSUbiquitousKeyValueStore는 작은 저빈도 키-값 상태에 예약하세요. 설정, 마지막으로 선택한 탭, 카운터, 기능 플래그 재정의입니다.
내 앱의 새 도메인에 어떤 기반을 선택할지 어떻게 알 수 있나요?
순서대로 세 가지 질문입니다. 첫째, iOS 앱 프로세스 외부의 어떤 것이 이 도메인을 읽거나 쓸 필요가 있나요? 그렇다면 iCloud Drive(파일 기반, 일반 파일 I/O) 또는 CloudKit(Apple의 프레임워크 또는 비-Apple 스택의 CloudKit Web Services를 통해) 또는 사용자가 제어하는 서버가 필요합니다. 그렇지 않다면 SwiftData가 기본값입니다. 둘째, 이것이 사용자의 장치 간에 동기화되어야 하나요? 그렇다면 기반이 그것을 지원해야 합니다(iCloud Drive는 지원하고, SwiftData는 iCloud 동기화와 짝지어지지 않으면 지원하지 않음). 셋째, 페이로드는 얼마나 크고 얼마나 자주 변경되나요? 작고 + 저빈도는 NSUbiquitousKeyValueStore에 살고, 그 외 모든 것은 실제 영속화 계층이 필요합니다.
참고 문헌
-
두 에이전트 생태계, 하나의 쇼핑 목록에서 저자의 분석, 2026년 4월 29일, 그리고 Get Bananas 프로젝트의
Banana List/iCloudBackupManager.swift에 있는 iCloud Drive JSON 동기화 계층. 이 아키텍처는 SwiftData를 외부 MCP 서버가 읽고 쓰는 사용자의 iCloud Drive 컨테이너에 있는 JSON 파일과 짝지웁니다. ↩↩ -
Apple Developer, “SwiftData” 및 “Adding and editing persistent data in your app”.
@Model매크로,@Attribute제약, 그리고 Core Data의NSManagedObjectModel과의 관계. 안전한 스키마 진화를 위한VersionedSchema와MigrationPlan은 SwiftData의 진짜 비용은 스키마 규율입니다에서 저자의 분석으로 다룹니다. ↩ -
Apple Developer, “Synchronizing documents in the iCloud environment”. 파일 기반 장치 간 동기화,
NSFileVersion을 통한 충돌 해결, 그리고 공유 파일에 대한 안전한 인프로세스 쓰기를 위한NSFileCoordinatorAPI. ↩ -
Apple Developer, “NSUbiquitousKeyValueStore”. 장치 간 키-값 저장소. Apple의 현재 한도: 앱당 총 1MB, 값당 1MB, 1024개 키, 키당 128 UTF-16 문자, 제한된 쓰기 속도. 이 API를 사용하여 Return이 출시한 장치 간 타이머 패턴은 다섯 개의 Apple 플랫폼, 세 개의 공유 파일에서 저자의 분석으로 다룹니다. ↩