{"id":25157586,"url":"https://github.com/podkovyrin/cloudkitnotes","last_synced_at":"2025-04-30T10:20:41.416Z","repository":{"id":77346116,"uuid":"187914466","full_name":"podkovyrin/CloudKitNotes","owner":"podkovyrin","description":"⛅️ Simple CloudKit based app that handles all possible errors","archived":false,"fork":false,"pushed_at":"2020-03-04T19:06:25.000Z","size":1174,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-30T15:11:59.401Z","etag":null,"topics":["cloudkit","demo","swift","swift5","sync"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/podkovyrin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-21T21:13:58.000Z","updated_at":"2024-12-31T03:17:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"b881adaa-fd11-4ca2-8865-ed07e6272dd7","html_url":"https://github.com/podkovyrin/CloudKitNotes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/podkovyrin%2FCloudKitNotes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/podkovyrin%2FCloudKitNotes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/podkovyrin%2FCloudKitNotes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/podkovyrin%2FCloudKitNotes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/podkovyrin","download_url":"https://codeload.github.com/podkovyrin/CloudKitNotes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251683402,"owners_count":21626960,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudkit","demo","swift","swift5","sync"],"created_at":"2025-02-09T01:40:52.946Z","updated_at":"2025-04-30T10:20:41.410Z","avatar_url":"https://github.com/podkovyrin.png","language":"Swift","readme":"\n\n# CloudKitNotes\n\nReal-world CloudKit usage example.\n\nThis app shows how to backup a simple object to CloudKit and handle all possible errors. CloudKitNotes started as a playground for testing CloudKit backup functionality for my upcoming app.  The idea behind is a dead-simple: allow the user to backup list of their private `Note`s which is Swift struct:\n\n```swift\nstruct Note {\n    var id: String \n    var text: String\n    var modified: Date\n}\n```\n\nThis example covers the following aspects of CloudKit:\n- Seamless synchronization process\n- Private CloudKit database usage\n- Silent push notifications to consume as less traffic as possible\n- Keeping data up to date regardless of disabled or undelivered push notifications\n- Handling all possible errors that might happen during synchronization (with minimal user interaction)\n- Allows user to enable or disable backup at any time\n- Respects user's privacy by deleting all data from iCloud when they want to disable backup\n\nOut of scope of this example project:\n- Public and shared database usage\n- Relationships between `CKRecord`s\n- Assets\n- Queries\n\nUnfortunately, it's almost impossible to write unit tests around CloudKit functionality so this app was tested manually by [several people](#special-thanks).\n\n## Requirements\n\n- Apple Developer Account\n- [CocoaPods](https://cocoapods.org)\n- Xcode ≥ 10.2 (Swift 5.0 is used)\n\n## Running\n\nTo run the project, clone the repo, and run `pod install` from the root directory first.\nUpdate Bundle ID to any new unique Bundle ID and let Xcode fix the signing issues.\n\n## How it works\n\nCloudKitNotes heavily uses [Advanced NSOperations](https://developer.apple.com/videos/play/wwdc2015/226/) technique as recommended in the [\"CloudKit Tips and Tricks\"](https://developer.apple.com/videos/play/wwdc2015/715/) WWDC session. It uses its own fork of [PSOperations](https://github.com/pluralsight/PSOperations) – [ANOperations](https://github.com/podkovyrin/CloudKitNotes/tree/master/CloudKitNotes/Advanced%20Operations).\n\n### Starting CloudKit\n\nFrom the user perspective backup should be enabled manually or on demand. We ask the user whether their wants to enable iCloud backup after adding the first note.\n\nVery first start of CloudKit syncing:\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/podkovyrin/CloudKitNotes/blob/master/assets/ck_first_start.png?raw=true\" alt=\"First CloudKit Start Diagram\"\u003e\n\u003c/p\u003e\n\nAll other subsequent starts of CloudKit syncing:\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/podkovyrin/CloudKitNotes/blob/master/assets/ck_regular_start.png?raw=true\" alt=\"Regular CloudKit Start Diagram\"\u003e\n\u003c/p\u003e\n\n### Architecture\n\nCloudKitNotes encapsulates `CKOperation`s into its own high level operations.\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/podkovyrin/CloudKitNotes/blob/master/assets/ck_classes.png?raw=true\" alt=\"CloudKitNotes Class Diagram\"\u003e\n\u003c/p\u003e\n\n## [CKError](https://developer.apple.com/documentation/cloudkit/ckerror) breakdown\n\nThis is a basic classification of different CKError codes. \n\n| Code | Raw Code | User Action Needed | Can I handle it? | Can retry? | Should happen only during development |\n|--------------------------------|:--------:|:-:|:---:|:---:|:-------------------------------------:|\n| internalError | 1 | ❌ | ❌ | ❌ |  |\n| partialFailure | 2 | ❌ | ✅ | ❌ |  |\n| networkUnavailable | 3 | ❌ | ✅ | ✅ |  |\n| networkFailure | 4 | ❌ | ✅ | ✅ |  |\n| badContainer | 5 | ❌ | ❌ | ❌ | ✅ |\n| serviceUnavailable | 6 | ❌ | ✅ | ✅ |  |\n| requestRateLimited | 7 | ❌ | ✅ | ✅ |  |\n| missingEntitlement | 8 | ❌ | ❌ | ❌ | ✅ |\n| notAuthenticated | 9 | ✅ | ❌ | ❌ |  |\n| permissionFailure | 10 | ✅ | ❌ | ❌ |  |\n| unknownItem | 11 | ❌ | ❌ | ❌ |  |\n| invalidArguments | 12 | ❌ | ✅ | ❌ |  |\n| resultsTruncated | 13 | ❌ | ✅ | ❌ |  |\n| serverRecordChanged | 14 | ❌ | ✅ | ❌ |  |\n| serverRejectedRequest | 15 | ❌ | ⚠️ | ⚠️ |  |\n| assetFileNotFound | 16 | ❌ | ✅ | ⚠️ |  |\n| assetFileModified | 17 | ❌ | ⚠️ | ❌ |  |\n| incompatibleVersion | 18 | ✅ | ❌ | ❌ |  |\n| constraintViolation | 19 | ❌ | ⚠️ | ❌ |  |\n| operationCancelled | 20 | ❌ | ✅ | ✅ |  |\n| changeTokenExpired | 21 | ❌ | ✅ | ❌ |  |\n| batchRequestFailed | 22 | ❌ | ✅ | ❌ |  |\n| zoneBusy | 23 | ❌ | ✅ | ✅ |  |\n| badDatabase | 24 | ❌ | ✅ | ❌ |  |\n| quotaExceeded | 25 | ⚠️ | ⚠️ | ❌ |  |\n| zoneNotFound | 26 | ❌ | ✅ | ❌ |  |\n| limitExceeded | 27 | ❌ | ✅ | ✅ |  |\n| userDeletedZone | 28 | ✅ | ⚠️ | ❌ |  |\n| tooManyParticipants | 29 | ❌ | ✅ | ❌ |  |\n| alreadyShared | 30 | ❌ | ⚠️ | ❌ |  |\n| referenceViolation | 31 | ❌ | ⚠️ | ❌ |  |\n| managedAccountRestricted | 32 | ⚠️ | ❌ | ❌ |  |\n| participantMayNeedVerification | 33 | ✅ | ✅ | ❌ |  |\n| serverResponseLost | 34 | ❌ | ✅ | ✅ |  |\n| assetNotAvailable | 35 | ❌ | ✅ | ✅ |  |\n\n\\* ⚠️ == It depends (It is up to your app how to handle those errors)\n\nBasically, you are able (*must*) handle most of errors however with some of them nothing you can do about it.  Just keep in mind that every CloudKit request might return any error and your code should be prepared to fail for an *unknown* reason.\n\nWhile handling retriable operations your app should implement a backoff timer or retry count logic so that it doesn't attempt the same operation repeatedly.\n\nThat's how CloudKitNotes handles global errors: [CloudKitErrorHandler.swift](https://github.com/podkovyrin/CloudKitNotes/blob/master/CloudKitNotes/CloudKit%20Storage/Internals/CloudKitErrorHandler.swift)\n\n## Special thanks\n\nSpecial thanks to [@k06a](https://github.com/k06a) and others who helped to manually test this app.\n\n## Author\n\nAndrew Podkovyrin, podkovyrin@gmail.com\n\n## Contribution\n\nFeel free to open [new issue](https://github.com/podkovyrin/CloudKitNotes/issues/new) or send a pull request.\n\n## License\n\nCloudNotes is available under the MIT license. See the LICENSE file for more info.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpodkovyrin%2Fcloudkitnotes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpodkovyrin%2Fcloudkitnotes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpodkovyrin%2Fcloudkitnotes/lists"}