스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
CKSyncEngine으로 iCloud와 동기화하기
CKSyncEngine을 사용해 사용자의 CloudKit 데이터를 iCloud에 동기화하는 방법을 알아보세요. 시스템이 동기화 작업 예약을 처리하게 함으로써, 앱이 사용하는 코드 양을 줄이는 법을 알려 드립니다. 또한 CloudKit이 개선될 때마다 자동으로 향상된 성능을 누리는 방법, 동기화 구현을 위한 테스트 등을 살펴보겠습니다. 이 세션을 최대한 활용하려면 CloudKit과 CKRecord 유형에 익숙해야 합니다.
챕터
- 0:00 - Intro
- 0:48 - The state of sync
- 3:09 - Meet CKSyncEngine
- 9:47 - Getting started
- 13:18 - Using CKSyncEngine
- 19:25 - Testing and debugging
- 22:27 - Wrap-up
리소스
관련 비디오
Tech Talks
-
다운로드
♪ ♪
안녕하세요, 저는 팀입니다 CloudKit 팀의 엔지니어죠 동료 아메르와 함께 새 CloudKit API인 CKSyncEngine을 소개하고자 합니다 CKSyncEngine은 기기와 클라우드의 데이터 동기화에 쓰이죠 먼저 Apple 플랫폼의 CloudKit 동기화 상태를 알아보고 CKSyncEngine의 개요 및 사용법을 살펴보겠습니다 그다음 여러분의 프로젝트에서 CKSyncEngine을 어떻게 구동하는지 알아보죠 준비가 끝나면 기기 간 데이터를 어떻게 동기화하는지 알려 드릴게요 마지막으로 CKSyncEngine 통합을 테스트하고 디버깅하는 모범 사례를 알아보겠습니다 첫 번째로 CloudKit의 개요를 살펴보죠
사용자는 새로운 앱에서 데이터가 동기화되길 기대합니다 iPhone에서 무언가를 만들면 Mac에서도 그걸 열 수 있길 원하죠 겉으로 보기엔 마치 마법 같은 일입니다 한곳에 있던 데이터가 어디에나 있으니까요 여러분과 저에게는 그렇게 간단한 일이 아니죠 CloudKit 자체는 복잡하지 않지만 동기화는 대체로 어렵습니다 여러 기기를 연동할 생각이라면 엇나갈 수 있는 게 많죠 따라서 동기화 코드는 간단할수록 좋습니다 동기화 코드를 간소화하려면 코드 작성을 최소화해야 하죠 다행히 CloudKit 동기화에 쓸 수 있는 훌륭한 API가 있습니다 이러한 API가 많은 작업을 대신 처리해 주죠 로컬 지속성을 포함하는 풀스택 솔루션을 원한다면 NSPersistentCloudKitContainer를 사용하면 됩니다 자체 로컬 지속성을 가져오고 싶다면 새 CKSyncEngine API를 사용하면 되죠 더 세밀하게 제어해야 할 때는 CKDatabase와 CKOperations를 쓰면 됩니다 CloudKit와 동기화해야 하지만 NSPersistentCloudKitContainer를 사용하지 않는다면 CKSyncEngine을 사용하면 됩니다 동기화에는 유동적인 부분이 많아서 CKSyncEngine처럼 고수준 API를 사용하면 복잡성을 줄일 수 있고 앱 동기화 경험도 개선되죠
동기화의 핵심은 한 기기에서 전송한 변경 사항을 다른 기기에서 가져오는 것입니다 필요시 CloudKit 레코드로 변환하고요 쉬워 보이지만 막상 그렇지도 않습니다 다양한 작업과 오류를 다 알아야 하며 시스템 조건을 관찰하고 계정 변경을 감지해야 하죠 또한 푸시 알림 처리 및 구독 관리 여러 가지 상태 추적 외에도 할 일이 많습니다 CKSyncEngine을 사용하면 작성해야 하는 동기화 코드의 양이 훨씬 적고 간결해집니다 앱에 속하는 부분만 처리하면 되고 나머지는 동기화 엔진이 처리하죠 제대로 된 동기화 엔진을 작성하려면 아마 코드를 수천 줄 써야 할 겁니다 테스트에서는 그 두 배를 쓰고요 사실 NSPersistent CloudKitContainer에 7만 줄 이상의 테스트 코드가 쓰였다는 소문을 들었어요 CKSyncEngine에도 상당히 많은 테스트 코드가 쓰였습니다 많은 작업을 여러분 대신 처리해야 하니까요 그래서 새 CKSyncEngine API란 뭘까요?
CKSyncEngine은 공통 로직을 캡슐화하여 CloudKit 데이터베이스와 동기화합니다 편리한 API를 제공하는 것이 목적이며 필요시 유연성을 제공하고자 하죠 사용자 지정 동기화 엔진을 직접 작성하는 앱 대부분의 요구를 충족하도록 설계되었습니다 일반적으로 앱의 전용 및 공유 데이터를 동기화할 때는 CKSyncEngine이 아주 적합합니다 동기화 엔진과 함께 사용하는 데이터 모델은 레코드와 영역으로 구성되는데 CloudKit의 나머지도 같은 유형의 데이터를 사용하죠 기존 CloudKit API로 데이터에 접근할 수 있습니다 그래서 기존에 구현한 CloudKit 동기화가 있다면 CKSyncEngine이 그것과도 동기화할 수 있죠 동기화 엔진은 시스템 전반의 여러 앱과 서비스에 사용됩니다 Freeform 앱도 그중 하나죠 다른 예시는 NSUbiquitousKeyValueStore로 동기화 엔진 위에 다시 작성되었는데요 하위 호환성을 갖춘 좋은 사례입니다 새 OS에서는 동기화 엔진을 사용하지만 지난 버전과도 동기화할 수 있죠 사용자 지정 CloudKit 동기화를 이미 구현해 놓으셨다면 CKSyncEngine으로 바꾸는 선택지도 있습니다 이 점에 끌린다면 전환하셔도 되지만 의무 사항은 아닙니다 유지해야 하는 코드가 적은 게 좋을 때도 있으니까요 또 CKSyncEngine이 개선될 때마다 이점을 누릴 수 있습니다 플랫폼이 발전하면 동기화 엔진도 발전하며 점점 더 쉽고 효율적으로 동기화가 이뤄질 겁니다 작아진 CKSyncEngine API 표면의 이점도 누려 보세요 앱의 특정 데이터 모델과 사용 예에 집중할 수 있죠 CKSyncEngine을 사용하고 싶지만 지원되지 않는 특정 기능이 필요하다면 원하는 대로 빌드해도 됩니다 하지만 CKSyncEngine의 새 기능으로 필요가 충족된다면 사용 예 피드백을 제출해 보세요 결국 동기화 엔진을 위한 훌륭한 아이디어는 여러분 같은 개발자가 생각해 내는 것이니까요 동기화 엔진은 어떻게 작동할까요? 보통 동기화 엔진의 역할은 앱과 CloudKit 서버 사이에서 데이터를 전달하는 겁니다 앱과 동기화 엔진은 레코드와 영역으로 통신하죠 저장할 변경 사항이 있으면 앱이 동기화 엔진에 전송합니다 다른 기기에서 변경 사항을 가져오면 해당 내용을 앱으로 전송하죠 그런데 동기화 엔진의 작업이 항상 즉각적으로 수행되는 건 아닙니다 서버와 통신해야 하면 시스템 작업 스케줄러와 먼저 상의합니다 OS에서 백그라운드 작업 관리에 쓰이는 것과 같은 스케줄러죠 스케줄러는 기기가 동기화 준비를 마쳤는지 확인합니다 기기가 준비되면 스케줄러가 작업을 실행하고 동기화 엔진은 서버와 통신합니다 이것이 동기화 엔진의 기본적인 작동 절차죠 동기화 엔진은 구체적으로 어떻게 서버로 변경 사항을 보낼까요? 먼저 누군가가 데이터를 수정합니다 무언가 적거나 스위치를 전환하거나 객체를 지우거나 하겠죠 그러면 앱이 동기화 엔진에 서버 전송 대기 중인 변경 사항이 있다고 알립니다 그렇게 동기화 엔진은 할 일이 있음을 알게 되죠 다음으로 동기화 엔진이 스케줄러에 작업을 제출합니다 기기가 준비되면 스케줄러가 작업을 실행하죠 작업이 실행되면 동기화 엔진은 서버로 변경 사항을 전송하는 프로세스를 시작합니다 이를 위해 다음 배치의 변경 사항 전송을 앱에 요청하죠 수정 사항이 하나라면 대기 변경 사항도 하나일 겁니다 하지만 방대한 양의 새 데이터를 불러왔다면 수백, 수천 가지의 변경 사항이 생기겠죠 요청 한 번에 서버로 전송 가능한 양에는 한계가 있으므로 동기화 엔진은 변경 사항을 배치로 요청합니다 실제로 필요할 때까지 레코드를 메모리로 가져오지 않기 때문에 메모리 오버헤드를 줄일 수 있죠 다음 배치를 제공하면 동기화 엔진이 서버로 보냅니다 서버는 작업 결과로 응답하는데요 변경이 완료됐는지 실패했는지 알려 주죠 요청이 완료되면 동기화 엔진이 앱으로 결과를 보냅니다 작업의 성공 또는 실패에 반응할 기회죠 대기 중인 변경 사항이 더 있다면 전송할 게 남지 않을 때까지 동기화 엔진이 배치를 요청합니다 한 기기에서 서버로 데이터를 보냈으니 다른 기기에서 그 데이터를 가져와야겠죠 서버가 새 변경 사항을 받으면 그 데이터에 접근하는 다른 기기들에 푸시 알림을 보냅니다 CKSyncEngine은 자동으로 앱에서 푸시 알림을 수신하죠 알림을 받으면 스케줄러에 작업을 제출합니다 스케줄러 작업이 실행되면 동기화 엔진이 서버에서 가져오죠 가져온 새 변경 사항은 앱으로 보냅니다 변경 사항을 로컬에 유지하고 UI에 표시할 수 있게 되죠 이게 동기화 엔진 사용의 기본 작업 절차입니다 이 절차의 공통 분모는 시스템 스케줄러입니다 일반적으로 CKSyncEngine은 작업 전에 스케줄러와 상의합니다 그러면 여러분 대신 자동으로 동기화할 수 있게 되죠
스케줄러는 네트워크 연결과 배터리양 리소스 사용량 등의 시스템 조건을 관찰합니다 동기화를 시도하기 전 기기가 필수 조건을 갖췄는지 확인하는 역할이죠 스케줄러를 따름으로써 동기화 엔진은 사용자 경험과 기기 리소스의 적절한 균형을 보장합니다 정상적인 조건에서는 아주 빠르게 동기화됩니다 몇 초도 안 걸리죠 하지만 네트워크 연결이 안 돼 있거나 기기의 배터리 잔량이 적다면 동기화가 지연되거나 유예될 수 있습니다 기기가 많은 작업을 처리 중이라면 동기화 메커니즘 때문에 앱의 급한 작업이 방해받지 않아야 하는데요 동기화 엔진의 자동 예약을 사용하면 동기화를 실행해야 할 때만 실행할 수 있습니다 더 효율적일 뿐 아니라 사용도 더 쉽죠 언제 동기화할지 걱정할 필요 없이 다른 일에 집중할 수 있습니다 하지만 수동 동기화에도 적절한 사용 예가 있습니다 변경 사항을 즉시 가져오는 '내려서 새로고침' UI나 대기 중인 변경 사항을 서버로 즉시 전송하는 백업 버튼 등이죠 수동 동기화는 자동 테스트를 작성할 때도 유용합니다 이벤트 순서를 제어해야 하는 특정 동기화 시나리오를 여러 기기에서 시뮬레이션할 수 있죠 보통 저희가 추천하는 건 자동 동기화 예약입니다 하지만 수동 동기화가 적합한 사례도 있으니 필요시에 사용할 API를 동기화 엔진에 탑재했습니다 이제 아메르가 CKSyncEngine 실행법을 알려 드리죠 소개해 줘서 고마워요, 팀 저는 아메르입니다 CloudKit 클라이언트 팀 엔지니어죠 이제 CKSyncEngine을 실행해 보겠습니다 CKSyncEngine을 사용하려면 프로젝트에 몇 가지를 준비해야 합니다 이러한 요구 사항은 CKSyncEngine을 사용할 때나 사용자 지정 CloudKit를 구현할 때나 동일합니다 우선 CloudKit의 핵심 데이터 유형과 CKRecord 및 CKRecordZone에 대한 기초 지식이 필요합니다 동기화 엔진 API는 레코드와 영역의 개념을 많이 다루므로 작업을 시작하기 전에 그 개념을 이해해야 합니다 다음으로 CloudKit 기능을 Xcode에서 활성화해야 합니다 또한 동기화 엔진은 푸시 알림으로 최신 상태를 유지하므로 원격 알림 기능도 활성화해야 합니다 모두 마쳤으면 동기화 엔진을 초기화할 준비가 된 거죠 CKSyncEngine의 초기화 시점은 앱 시작 직후가 좋습니다 동기화 엔진을 초기화하면 자동으로 푸시 알림과 스케줄러 작업을 백그라운드에서 수신하기 시작하죠 알림과 작업은 언제든 발생할 수 있으니 이를 처리하기 위해 동기화 엔진을 초기화해야 합니다
앱과 CKSyncEngine이 통신하는 주요 수단은 CKSyncEngineDelegate라는 프로토콜입니다 동기화 엔진을 초기화할 때 이 프로토콜을 준수하는 객체를 제공해야 하죠 올바르고 효율적으로 동작하기 위해 동기화 엔진은 몇 가지 내부 상태를 추적합니다 또한 동기화 엔진 상태의 마지막 버전도 제공해야 하죠
동기화 작업을 수행하는 동안 때때로 대리자에게 상태의 업데이트 버전이 업데이트 이벤트 형태로 제공됩니다 동기화 엔진이 새 상태를 직렬화하면 로컬로 지속해야 합니다 그러면 다음에 프로세스를 시작하면서 동기화 엔진을 초기화할 때 이를 제공할 수 있죠 이해를 돕기 위해 몇 가지 코드 예제를 볼게요
동기화 엔진을 초기화하려면 구성 객체를 전달해야 합니다 구성에는 동기화하고 싶은 데이터베이스와 동기화 엔진 상태의 마지막 버전 그리고 대리자를 제공해야 합니다 대리자 프로토콜에는 handleEvent 함수가 있습니다 이 함수를 통해 동기화 엔진이 정상적인 동기화 작업 중 생겨나는 다양한 이벤트를 앱에 알리죠 예를 들어 서버에서 새 데이터를 가져오거나 계정이 변경될 때 이벤트를 게시합니다 상태 업데이트 이벤트도 그중 하나입니다 동기화 엔진이 내부 상태를 업데이트하거나 여러분이 직접 상태를 업데이트하면 동기화 엔진이 상태 업데이트 이벤트를 게시합니다 이벤트에 대한 응답으로 새로 직렬화된 상태 버전을 로컬로 지속해야 하죠 이 예제에서는 다음 동기화 엔진 초기화 시 이 상태 직렬화를 사용하게 돼 있습니다 기본 설정을 마쳤으니 동기화 엔진으로 동기화하는 법을 알아보죠
서버로 변경 사항을 보낼 때는 간단한 몇 단계를 거칩니다 첫째, 대기 중인 레코드 영역 변경 사항과 데이터베이스 변경 사항을 동기화 엔진 상태에 추가하세요 그러면 동기화 엔진에 동기화를 예약하도록 알릴 수 있습니다 엔진은 일관성을 보장하고 중복 변경 사항을 제거하죠
둘째, 대리자 메서드인 nextRecordZoneChangeBatch를 구현합니다 동기화 엔진은 이를 호출하여 서버로 보낼 레코드 영역 변경의 다음 배치를 얻게 되죠 마지막으로 sentDatabaseChanges와 sentRecordZoneChanges 이벤트를 처리하세요 변경 사항이 서버에 게시되면 이러한 이벤트가 생겨나죠
서버로 변경 사항을 보내는 예제입니다 이 앱은 데이터를 편집하고 새 레코드 변경을 동기화하려 합니다 그러려면 대기 중인 레코드 영역 변경 사항을 동기화 엔진 상태에 추가하여 레코드를 저장해야 하죠 동기화 엔진이 레코드를 동기화할 준비가 되면 nextRecordZoneChangeBatch라는 대리자 메서드를 부릅니다 여기서 서버로 보낼 다음 변경 사항 배치를 반환하죠 RecordZoneChangeBatch를 초기화하려면 대기 중인 변경 사항 목록과 레코드 공급자를 제공하세요 대기 중인 변경 사항 목록에는 저장 또는 삭제할 recordID와 실제 동기화가 발생할 때 해당 ID를 레코드로 매핑할 레코드 공급자가 포함됩니다 앱이 서버에서 변경 사항을 가져오는 방법은 이렇습니다 동기화 엔진이 자동으로 서버에서 변경 사항을 가져옵니다 그러고 나서 fetchedDatabaseChanges와 fetchedRecordZoneChanges 이벤트를 게시하죠 경우에 따라 willFetchChanges와 didFetchChanges 이벤트를 수신하면 좋습니다 예를 들어 변경 내용을 가져오기 전이나 후에 설정이나 정리 작업을 수행하고 싶다면 이런 이벤트를 처리하는 게 유용할 수 있죠 앱이 서버에서 변경 사항을 가져오는 예입니다 동기화 엔진은 레코드 영역의 변경 사항을 가져올 때 fetchedRecordZoneChanges 이벤트를 게시합니다 이 이벤트는 다른 기기에서 수행된 수정 및 삭제를 포함하죠 이를 수신할 때는 가져온 수정 사항과 삭제 사항을 살펴봐야 합니다 수정 사항을 받으면 데이터를 로컬로 지속해야 하고 삭제 사항을 받으면 로컬에서도 삭제해야 합니다 데이터베이스 변경을 가져오는 작업도 비슷하며 같은 접근 방식으로 처리할 수 있습니다 오류를 처리하는 건 어렵죠 동기화 엔진은 오류 처리도 도와줍니다 동기화 엔진은 자동으로 일시적 오류를 처리합니다 네트워크 문제, 스로틀링 계정 문제 등이죠 동기화 엔진은 오류에 영향받은 작업을 자동으로 재실행합니다 다른 오류는 앱이 처리해야 하죠 오류를 해결하고 나면 필요한 경우 작업 예약을 변경해야 합니다
레코드 영역 변경 전송 시 오류를 처리하는 예제입니다 sentRecordZoneChanges 이벤트가 게시되면 failedRecordSaves로 저장 실패 한 레코드를 확인합니다
serverRecordChanged는 서버 내 변경 레코드를 말합니다 앱이 아직 가져오지 않은 새 버전을 다른 기기에서 저장했다는 뜻이죠 충돌을 해결하고 예약을 변경해야 합니다
zoneNotFound는 이 영역이 아직 서버에 없다는 뜻입니다 이를 해결하려면 영역을 만든 다음 작업을 다시 예약해야 하죠 동기화 엔진은 항상 영역을 먼저 저장하고 그다음에 레코드를 저장합니다 networkFailure와 networkUnavailable serviceUnavailable requestRateLimited는 동기화 엔진이 처리하는 일시적 오류의 예시입니다 알림 차원에서 오류를 표시하지만 조치를 취해서 대응할 필요는 없습니다 시스템 조건이 허용하면 동기화 엔진이 자동으로 오류가 난 사항을 재시도하죠
계정 변경 역시 동기화 엔진으로 쉬워집니다 기기의 iCloud 계정은 언제든 변경될 수 있는데요 동기화 엔진으로 관리하고 반응할 수 있습니다 동기화 엔진은 변경을 감지하고 알려 줍니다 accountChange 이벤트로 로그인, 로그아웃 계정 변경까지 알려 주죠 앱은 변경 사항의 유형에 따라 대비해야 합니다
동기화 엔진은 기기에 계정이 나타날 때까지 iCloud 동기화를 시작하지 않습니다
동기화 엔진은 언제든 초기화할 수 있으며 계정이 변경되면 자동으로 업데이트를 보냅니다 다른 사용자와의 데이터 공유는 CloudKit의 핵심인데요 이 또한 동기화 엔진으로 더 쉬워졌습니다 CloudKit 공유 데이터베이스와 동기화 엔진이 함께 작동하죠 앱이 작업할 데이터베이스마다 동기화 엔진을 만들면 됩니다 예를 들어 프라이빗 데이터베이스용 동기화 엔진과 공유 데이터베이스용 동기화 엔진을 따로 만드는 거죠 CloudKit 공유에 대한 자세한 정보는 'CloudKit 공유 최대한 활용하기' Tech Talk를 확인하세요
CKSyncEngine 사용법을 알아봤습니다 이제 사용 시 테스트하는 법을 알려 드리죠 신속한 개발에서 코드베이스의 안정성을 확보하는 최적의 방법은 자동 테스트입니다 동기화 엔진을 통해 기기 간 사용자 절차를 시뮬레이션할 수 있습니다 여러 CKSyncEngine 인스턴스를 사용해서요
앱에 발생 가능한 극단적 경우도 시뮬레이션해야 하는데요 그러려면 automaticallySync를 false로 설정하여 동기화 엔진 절차에 개입하면 됩니다 다음은 두 기기와 서버 간의 데이터 충돌을 보여 주는 테스트 사례입니다 이 테스트의 목적은 여러 기기에서 작업 시 사용자가 거치는 전체 절차를 시뮬레이션하는 거죠 충돌 해결도 확인하고요 먼저 MySyncManager로 두 기기를 시뮬레이션합니다 이 예제에서 MySyncManager는 로컬 데이터베이스와 동기화 엔진을 만들죠 기기 A는 값을 A로 설정하고 변경 사항을 서버로 보냅니다
기기 B가 서버에서 변경 사항을 가져오기 전에 B의 변경 사항도 서버로 보낼 것을 요청합니다 기기 A가 먼저 서버에 저장했기 때문에 기기 B의 저장은 실패할 것으로 예상되죠 이로 인해 서버 레코드 변경 오류가 발생하고 로컬 충돌 해결 코드가 실행됩니다 이 예제에서는 충돌 해결 시 서버 데이터를 우선으로 사용할 것을 기대하므로 기기 B의 새로운 값은 기기 A가 가장 최근에 서버로 보낸 값이 되죠
빠른 테스트와 디버깅을 위한 몇 가지 중요한 점이 있습니다 각 기기의 이벤트 시퀀스를 이해하면 어떤 절차에서 앱이 문제를 겪는지 쉽게 파악할 수 있습니다 개발 시 최대한 많이 로그하면 절차 추적에 도움이 되고 여러 기기의 로그를 비교할 수 있습니다 수신하는 각 이벤트를 CloudKit가 로그하지만 앱에서도 그 주변 동작을 로그해야 합니다
레코드 ID와 영역 ID를 로그하면 동기화 엔진과 서버 그리고 동기화 중인 다른 기기 간의 데이터 흐름을 쉽게 디버깅할 수 있죠
각 사용자 절차를 시뮬레이션하는 테스트를 작성하면 코드베이스를 확장할 때 안정성을 유지할 수 있습니다
퍼즐을 맞출 때는 타임스탬프를 확인하세요 소수의 동기화 작업만 발생할 수도 있고 단시간에 많은 작업이 발생할 수도 있습니다 여러 기기 간 디버깅의 핵심은 올바른 작업을 추적하는 거죠
이러한 단계를 거치면 CKSyncEngine을 사용해 믿음직하고 오래가는 앱을 제작 및 유지할 수 있습니다
CKSyncEngine 설명은 여기까지입니다 동기화 엔진 샘플 코드를 통해 전체 동작 예시를 확인해 보세요 더 자세한 내용은 CKSyncEngine 문서를 확인하세요 동기화 엔진을 개선할 의견이 있으시다면 CloudKit 팀에 피드백을 제출해 주세요 여러분의 작품이 기대됩니다 시청해 주셔서 감사합니다 즐거운 WWDC 보내세요 ♪ ♪
-
-
12:14 - Initializing CKSyncEngine
actor MySyncManager : CKSyncEngineDelegate { init(container: CKContainer, localPersistence: MyLocalPersistence) { let configuration = CKSyncEngine.Configuration( database: container.privateCloudDatabase, stateSerialization: localPersistence.lastKnownSyncEngineState, delegate: self ) self.syncEngine = CKSyncEngine(configuration) } func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async { switch event { case .stateUpdate(let stateUpdate): self.localPersistence.lastKnownSyncEngineState = stateUpdate.stateSerialization } } }
-
14:13 - Sending changes to the server
func userDidEditData(recordID: CKRecord.ID) { // Tell the sync engine we need to send this data to the server. self.syncEngine.state.add(pendingRecordZoneChanges: [ .save(recordID) ]) } func nextRecordZoneChangeBatch( _ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine ) async -> CKSyncEngine.RecordZoneChangeBatch? { let changes = syncEngine.state.pendingRecordZoneChanges.filter { context.options.zoneIDs.contains($0.recordID.zoneID) } return await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: changes) { recordID in self.recordToSave(for: recordID) } }
-
15:40 - Fetching changes from the server
func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async { switch event { case .fetchedRecordZoneChanges(let recordZoneChanges): for modifications in recordZoneChanges.modifications { // Persist the fetched modification locally } for deletions in recordZoneChanges.deletions { // Remove the deleted data locally } case .fetchedDatabaseChanges(let databaseChanges): for modifications in databaseChanges.modifications { // Persist the fetched modification locally } for deletions in databaseChanges.deletions { // Remove the deleted data locally } // Perform any setup/cleanup necessary case .willFetchChanges, .didFetchChanges: break case .sentRecordZoneChanges(let sentChanges): for failedSave in sentChanges.failedRecordSaves { let recordID = failedSave.record.recordID switch failedSave.error.code { case .serverRecordChanged: if let serverRecord = failedSave.error.serverRecord { // Merge server record into local data syncEngine.state.add(pendingRecordZoneChanges: [ .save(recordID) ]) } case .zoneNotFound: // Tried to save a record, but the zone doesn't exist yet. syncEngine.state.add(pendingDatabaseChanges: [ .save(recordID.zoneID) ]) syncEngine.state.add(pendingRecordZoneChanges: [ .save(recordID) ]) // CKSyncEngine will automatically handle these errors case .networkFailure, .networkUnavailable, .serviceUnavailable, .requestRateLimited: break // An unknown error occurred default: break } } case .accountChange(let event): switch event.changeType { // Prepare for new user case .signIn: break // Delete local data case .signOut: break // Delete local data and prepare for new user case .switchAccounts: break } } }
-
18:49 - Using CKSyncEngine with private and shared databases
let databases = [ container.privateCloudDatabase, container.sharedCloudDatabase ] let syncEngines = databases.map { var configuration = CKSyncEngine.Configuration( database: $0, stateSerialization: lastKnownSyncEngineState($0.databaseScope), delegate: self ) return CKSyncEngine(configuration) }
-
20:00 - Testing CKSyncEngine integration
func testSyncConflict() async throws { // Create two local databases to simulate two devices. let deviceA = MySyncManager() let deviceB = MySyncManager() // Save a value from the first device to the server. deviceA.value = "A" try await deviceA.syncEngine.sendChanges() // Try to save the value from the second device before it fetches changes. // The record save should fail with a conflict that includes the current server record. // In this example, we expect the value from the server to win. deviceB.value = "B" XCTAssertThrows(try await deviceB.syncEngine.sendChanges()) XCTAssertEqual(deviceB.value, "A") }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.