I have an issue in my app, where the crashing frame is an assertionFailure in BackingData.set inside SwiftData framework. My own app doesn't appear until frame 14. I have no idea what causes this, or even how to create a reproducible project as this only happens on some devices.
The frame prior to the assertionFailure is this:
#1 (null) in BackingData.set(any:value:) ()
It seems like there is a backing data encoding happening in my Model class, and some value is causing it to fail. The model being accessed is through a relationship, and the frame in the app crashing is along the lines of
Text(parent.child.name)
Obviously, something is wrong in how I have made child, but the part that stand out to me is the assertionFailure in a release build
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
In my app, I've been using ModelActors in SwiftData, and using actors with a custom executor in Core Data to create concurrency safe services.
I have multiple actor services that relate to different data model components or features, each that have their own internally managed state (DocumentService, ImportService, etc).
The problem I've ran into, is that I need to be able to use multiple of these services within another service, and those services need to share the same context. Swift 6 doesn't allow passing contexts across actors.
The specific problem I have is that I need a master service that makes multiple unrelated changes without saving them to the main context until approved by the user.
I've tried to find a solution in SwiftData and Core Data, but both have the same problem which is contexts are not sendable. Read the comments in the code to see the issue:
/// This actor does multiple things without saving, until committed in SwiftData.
@ModelActor
actor DatabaseHelper {
func commitChange() throws {
try modelContext.save()
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// Next, use our item service
let service = ItemService(modelContainer: SwiftDataStack.shared.container)
let id = try await service.expensiveBackgroundTask(saveChanges: false)
// Now that we've used the service, we need to access something the service created.
// However, because the service created its own context and it was never saved, we can't access it.
let itemFromService = context.fetch(id) // fails
// We need to be able to access changes made from the service within this service,
/// so instead I tried to create the service by passing the current service context, however that results in:
// ERROR: Sending 'self.modelContext' risks causing data races
let serviceFromContext = ItemService(context: modelContext)
// Swift Data doesn't let you create child contexts, so the same context must be used in order to change data without saving.
}
}
@ModelActor
actor ItemService {
init(context: ModelContext) {
modelContainer = SwiftDataStack.shared.container
modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
func expensiveBackgroundTask(saveChanges: Bool = true) async throws -> PersistentIdentifier? {
// Do something expensive...
return nil
}
}
Core Data has the same problem:
/// This actor does multiple things without saving, until committed in Core Data.
actor CoreDataHelper {
let parentContext: NSManagedObjectContext
let context: NSManagedObjectContext
/// In Core Data, I can create a child context from a background context.
/// This lets you modify the context and save it without updating the main context.
init(progress: Progress = Progress()) {
parentContext = CoreDataStack.shared.newBackgroundContext()
let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = parentContext
self.context = childContext
}
/// To commit changes, save the parent context pushing them to the main context.
func commitChange() async throws {
// ERROR: Sending 'self.parentContext' risks causing data races
try await parentContext.perform {
try self.parentContext.save()
}
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// As with the Swift Data example, I am unable to create a service that uses the current actors context from here.
// ERROR: Sending 'self.context' risks causing data races
let service = ItemService(context: self.context)
}
}
Am I going about this wrong, or is there a solution to fix these errors?
Some services are very large and have their own internal state. So it would be very difficult to merge all of them into a single service. I also am using Core Data, and SwiftData extensively so I need a solution for both.
I seem to have trapped myself into a corner trying to make everything concurrency save, so any help would be appreciated!
When I tried to use a working project with iOS 18 installed on my device, it wouldn't work anymore and crash right away. Before with iOS 17 it was working fine.
I can't access child variables that are saved in an Array in a parent object in SwiftData. The error is always somewhere in these hidden lines:
{
@storageRestrictions(accesses: _$backingData, initializes: _title)
init(initialValue) {
_$backingData.setValue(forKey: \.title, to: initialValue)
_title = _SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.title)
return self.getValue(forKey: \.title)
}
set {
_$observationRegistrar.withMutation(of: self, keyPath: \.title) {
self.setValue(forKey: \.title, to: newValue)
}
}
}
The child classes are also inserted and saved into the modelContext when created and set to the parent instance, but I also can't fetch them via modelContext.fetch() - Error here is:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x243a62a4c)
Maybe there is a problem with the relationship between two saved instances.
The parent instances are saved correctly and it was working in iOS 17.
The problem is similar to these two cases:
https://forums.developer.apple.com/forums/thread/762679
https://forums.developer.apple.com/forums/thread/738983
I changed the logic after I reviewed these threads, as I am now linking the parent and child instances, that got rid of one warning in the console.
button.canvas = canvas
modelContext.insert(button)
canvas.buttons = [button]
But in the end those threads were not enough for me to find a fix for my problem.
A small project can be found here:
https://github.com/DonMalte/SwiftDataTest
Topic:
App & System Services
SubTopic:
iCloud & Data
Hi all,
As you know, when using SwiftData Cloudkit, all relationships are required to be optional.
In my app, which is a list app, I have a model class Project that contains an array of Subproject model objects. A Subproject also contains an array of another type of model class and this chain goes on and on.
In this type of pattern, it becomes really taxxing to handle the optionals the correct way, i.e. unwrap them as late as possible and display an error to the user if unable to.
It seems like most developers don't even bother, they just wrap the array in a computed property that returns an empty array if nil.
I'm just wondering what is the recommended way by Apple to handle these optionals. I'm not really familiar with how the CloudKit backend works, but if you have a simple list app that only saves to the users private iCloud, can I just handwave the optionals like so many do? Is it only big data apps that need to worry? Or should we always strive to handle them the correct way? If that's the case, why does it seem like most people skip over them? Be great if an Apple engineer could weigh in.
Hi, thank you for your reply. I have checked and confirmed that all AppleUser entity fields (id, name, email, password, createdAt) are optional, relationships (posts, comments) are optional, and I assign values when creating a new object, but Core Data still throws a nilError during registration; I have uploaded my project to GitHub for your reference here: https://github.com/Kawiichao/job. If reviewing it requires any payment, please let me know in advance. Thank you very much for your kind offer—I really appreciate it!
Topic:
App & System Services
SubTopic:
iCloud & Data
I have an app which uses ubiquitous containers and files in them to share data between devices. It's a bit unusual in that it indexes files in directories the user grants access to, which may or may not exist on a second device - those files are identified by SHA-1 hash. So a second device scanning before iCloud data has fully sync'd can create duplicate references which lead to an unpleasant user experience.
To solve this, I store a small binary index in the root of the ubiquitous file container of the shared data, containing all of the known hashes, and as the user proceeds through the onboarding process, a background thread is attempting to "prime" the ubiquitous container by calling FileManager.default.startDownloadingUbiquitousItemAt() for each expected folder and file in a sane order.
This likely creates a situation not anticipated by the iOS/iCloud integration's design, as it means my app has a sort of precognition of files it should not yet know about.
In the common case, it works, but there is a corner case where iCloud sync has just begun, and very, very little metadata is available (the common case, however, in an emulator), in which two issues come up:
I/O may hang indefinitely, trying to read a file as it is arriving. This one I can work around by running the I/O in a thread created with the POSIX pthread_create and using pthread_cancel to kill it after a timeout.
Attempts to call FileManager.default.startDownloadingUbiquitousItemAt() fails with an error Error Domain=NSCocoaErrorDomain Code=257 "The file couldn’t be opened because you don’t have permission to view it.". The permissions aspect of it is nonsense, but I can believe there's no applicable "sort of exists, sort of doesn't" error code to use and someone punted. The problem is that this same error will be thrown on any attempt to access that file for the life of the application - a restart is required to make it usable.
Clearly, the error or the hallucinated permission failure is cached somewhere in the bowels of iOS's FileManager. I was hoping startAccessingSecurityScopedResource() would allow me to bypass such a cache, as it does with URL.resourceValues() returning stale file sizes and last modified times. But it does not.
Is there some way to clear this state without popping up a UI with an Exit button (not exactly the desired iOS user experience)?
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
Foundation
Files and Storage
iOS
iCloud Drive
hi,
in my app, i have created and pushed CKRecords to the public database. others using the app have pushed CKRecords as well.
is there any way i can query iCloud for "all the CKRecords that i created?"
thanks,
DMG
Topic:
App & System Services
SubTopic:
iCloud & Data
LSUB always returns all the subscribed folders. For example
lsub "" "test/*"
returns a list of all the folders and not just subscribed folders that are subfolders of test. I.e, it returns the same folder list as
lsub "" "*".
For more details please see https://bugzilla.mozilla.org/show_bug.cgi?id=1817707#c15
I have an app that I signed and distribute between some internal testflight users. Potentially I want to invite some 'Public' beta testers which don't need to validate (_World have read rights in the public database)
Question: Do I need to have a working public CloudKit , when users are invited through TestFlight, or are they going to test on the development container?
I understand that when I invite beta-tester without authorization (external testers) they cannot access the developer container, so therefore I need to have the production CloudKit container up and running.
I have tried to populate the public production container, but for whatever reason my upload app still goes to the development container. I have archived the app, and tried, but no luck. I let xcode manage my certificates/profiles. but what do I need to change to be able to use my upload file to upload the production container, instead of the development.
I tried:
init() {
container = CKContainer(identifier: "iCloud.com.xxxx.xxxx")
publicDB = container.publicCloudDatabase
I got no error in the console, but data is always populated to the development database, instead the production.
I tried to create a provisioning profile, but for some reason Xcode doesn't like it. Tried to create one a different provisioning profile manual through the developer portal, for the app. but xcode doesn't want to use that, and mentions that the requirement are already in place.
What can I check/do to solve this.
Hey there,
Can we bundle our app with our own version of SQLite with extensions that we want. From what I've seen, we aren't allowed to add extensions to the built in IOS SQLite, so would this be the only way to use extensions. I ask this because I want to use the spell fix extension.
I couldn't find a lot of people talking about adding SQLite extensions.
Thank you!
Topic:
App & System Services
SubTopic:
iCloud & Data
I implemented the cloudkit function, where users can connect with each other. The problem is, that if User A is doing a friend request and User B is accepting the request. The friend entry is correct visible for User B but not for User A. I can see in cloud kit that after the accepted request, the friend connection is set up correctly, also with the correct userID, but it not showing up for User A (the one that send the request)
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
CloudKit Dashboard
CloudKit Console
In Apple Numbers and similar apps, a user can save a document to iCloud Drive, and collaborate with other users. From what I can gather, it seems to use two mechanisms: the document as a whole is synced via iCloud Drive, but when a collaboration is started, it seems to use CloudKit records to do live updates.
I am working on a similar app, that saves documents to iCloud Drive (on Mac, iPad, and iPhone). Currently it only syncs via iCloud Drive, re-reading the entire (often large) document when a remote change occurs. This can lead to a delay of several seconds (up to a minute) for the document to be saved, synced to the server, synced from the server, and re-read.
I'm working on adding a "live sync", i.e. the ability to see changes in as near to real-time as feasible, like in Apple's apps.
The document as a whole will remain syncing via iCloud Drive. My thought is to add a CloudKit CKRecord-based sync when two or more users are collaborating on a document, recording only the diffs for quick updates. The app would no longer re-read the entire document when iCloud Drive updates it while in use, and would instead read the CloudKit records and apply those changes. This should be much faster.
Is my understanding of how Apple does it correct? Does my proposed approach seem sensible? Has anyone else implemented something like this, with iCloud Drive-based documents and a CloudKit live sync?
In terms of technologies, I see that Apple now has a Shared with You framework, with the ability to use a NSItemProvider to start the collaboration. Which raises the question, should I use the iCloud Drive document for the collaboration (as I do now), or the CloudKit CKShare diff? I think I'd have to use the document as a whole, both so it works with the Send Copy option, and so a user that doesn't have the document gets it when using Collaborate. Once the collaboration is underway, I'd want to start the CloudKit channel. So I guess I'd save the CKShare to the server, get its URL, and save that in the document, so another user can read that URL as part of their initial load of the document from iCloud Drive?
Once two (or more) users have the document via iCloud Drive, and the CKShare via the embedded URL, I should be able to do further live-sync updates via CloudKit. If a user closes the document and re-opens it, they'd get the updates via iCloud Drive, so no need to apply any updates from before the document was opened.
Does all this sound reasonable, or am I overlooking some gotcha? I'd appreciate any advice from people who have experience with this kind of syncing.
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
Cloud and Local Storage
iCloud Drive
Hi,
I’m running into an issue with Core Data migrations using a custom NSMappingModel created entirely in Swift (not using .xcmappingmodel files).
Setup:
• I’m performing a migration with a manually constructed NSMappingModel
• One of the NSEntityMapping instances is configured as follows:
• mappingType = .customEntityMappingType (or .transformEntityMappingType)
• entityMigrationPolicyClassName is set to a valid subclass of NSEntityMigrationPolicy
• The class implements the expected methods like:
@objc func createDestinationInstances(…) throws { … }
@objc func createCustomDestinationInstance(…) throws -> NSManagedObject { … }
The policy class is instantiated (confirmed via logging in init()),
but none of the migration methods are ever called.
I have also tried adding valid NSPropertyMapping instances with real valueExpression bindings to force activation, but that didn’t make a difference.
Constraints:
• I cannot use .xcmappingmodel files in this context due to transformable attributes not compatible with the visual editor.
• Therefore, I need the entire mapping model to be defined in Swift.
Workaround:
As a temporary workaround, I’m migrating the data manually using two persistent stores and NSManagedObjectContext, but I’d prefer to rely on NSMigrationManager as designed.
Question:
Is there a known limitation that prevents Core Data from invoking NSMigrationPolicy methods when using in-memory NSMappingModel instances?
Or is there any specific setup required to trigger them when not loading from .xcmappingmodel?
Thanks in advance.
I am using cloudkit to save users high scores in a public database. The preference over using Game Center is because of simplicity and works really well for what I want to achieve. I simply want to let users know their global position. Because of data privacy laws the app asks the user for their permission to submit their score each time they get a new high score.
However, I have noticed that CKRecords under 'created' and 'modified' in addition to UTC time and date also contain creatorUserRecordID. Could this be a privacy issue? Can you extract any personal information from this? Can this be used to track back to the user? Is it linked to CKUserIdentity which I understand does contain personal information, although as I understand you need users consent to get this information. Under creatorUserRecordID it says... "Every user of the app has a unique user record that is empty by default. Apps can add data to the user record on behalf of the user, but don’t store sensitive data in it"
Currently I simply ask the user if they are happy to submit their score. But do I need to point out that it also stores a creatorUserRecordID? Obviously I don't want to do this if it is not needed as the user will 1) Probably not understand what a creatorUserRecordID is and 2) It makes the question complicated and will likely make most people refuse to submit their score.
If it is a privacy issue, is there anyway to stop a CKRecord creating this ID and simply save a score? All I need is a list of scores so the app can determine their current position.
If creatorUserRecordID does not contain any personal details and cannot be tracked back to the user please let me know, so I can be reassured that my current set up is fine and I am not causing any privacy issues!
This post did seem to indicate you may possibly be able to fetch personal details??
https://stackoverflow.com/questions/55782166/how-do-i-fetch-any-info-about-user-that-modified-ckrecord
I have been trying to get this to work since it was announced a few years ago but with no joy. I'm struggling to get Apple's example code to behave itself too. Seems overly complex and buggy. So I set out to create a simplified version myself. I have got the database to sync with CloudKit and I can see my records in the developer dashboard. I'm trying to use container.record(for: object.objectID) to get the CKRecord for it, but this always fails. The next step would be to add the participant.
I try to add the participant based on this code:
Button
{
let record = fetchRecord(for: items[0]) //hack just to use the first record for dev testing
let share = CKShare(rootRecord: record)
let persistenceController = PersistenceController.shared
persistenceController.addParticipant(
emailAddress: "andrew@ambrit.com",
permission: .readWrite,
share: share)
{ share, error in
if let error = error
{
print("Error: \(error.localizedDescription)")
}
else if let share = share
{
print("Share updated successfully: \(share)")
}
}
}
label:
{
Label("Participants", systemImage: "person")
}
and
extension PersistenceController
{
func addParticipant(emailAddress: String, permission: CKShare.ParticipantPermission = .readWrite, share: CKShare,
completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)?)
{
let container = PersistenceController.shared.container
let lookupInfo = CKUserIdentity.LookupInfo(emailAddress: emailAddress)
let persistentStore = privatePersistentStore //share.persistentStore!
container.fetchParticipants(matching: [lookupInfo], into: persistentStore) { (results, error) in
guard let participants = results, let participant = participants.first, error == nil else
{
completionHandler?(share, error)
return
}
participant.permission = permission
participant.role = .privateUser
share.addParticipant(participant)
container.persistUpdatedShare(share, in: persistentStore)
{ (share, error) in
if let error = error
{
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
}
My immediate problem is that when I call fetchRecord it doesn't find anything despite the record being available in the CloudKit dashboard.
func fetchRecord(for object: NSManagedObject) -> CKRecord
{
let container = PersistenceController.shared.container
print ("Fetching record \(object.objectID)")
if let record = container.record(for: object.objectID)
{
print("CKRecord ID: \(record.recordID)")
print("Record Name: \(record.recordID.recordName)")
return record
}
else
{
fatalError("Record not found")
}
}
I’m getting a 0xdead10cc crash in a basic CoreData/CloudKit application. I only have one CoreData save call and its made when the app is in the foreground and it's minor so I don't think its being caused by that. My best guess is that it's related to background syncing of CloudKit. Does anyone know how to fix it? I've been advised that adding the following code around any saves will fix it, but it seems weird that this is the solution. I would expect the inner CoreData/CloudKit engine to handle this.
ProcessInfo().performActivity(reason: "Persisting to context") {
// Save to context here
}
Here is the crashing thread
Thread 7:
0 libsystem_kernel.dylib 0x00000001edc086f4 guarded_pwrite_np + 8 (:-1)
1 libsqlite3.dylib 0x00000001ca71b6e4 seekAndWrite + 456 (sqlite3.c:44287)
2 libsqlite3.dylib 0x00000001ca6d5df4 unixWrite + 180 (sqlite3.c:44365)
3 libsqlite3.dylib 0x00000001ca723b90 pagerWalFrames + 872 (sqlite3.c:67093)
4 libsqlite3.dylib 0x00000001ca6d5b14 sqlite3PagerCommitPhaseOne + 316 (sqlite3.c:70409)
5 libsqlite3.dylib 0x00000001ca6c6494 sqlite3BtreeCommitPhaseOne + 172 (sqlite3.c:81106)
6 libsqlite3.dylib 0x00000001ca6c605c vdbeCommit + 1136 (sqlite3.c:94124)
7 libsqlite3.dylib 0x00000001ca69f778 sqlite3VdbeHalt + 1340 (sqlite3.c:94534)
8 libsqlite3.dylib 0x00000001ca6c0618 sqlite3VdbeExec + 42648 (sqlite3.c:103922)
9 libsqlite3.dylib 0x00000001ca6b56c0 sqlite3_step + 960 (sqlite3.c:97886)
10 CoreData 0x00000001a459ab38 _execute + 128 (NSSQLiteConnection.m:4614)
11 CoreData 0x00000001a45fe004 -[NSSQLiteConnection commitTransaction] + 728 (NSSQLiteConnection.m:3278)
12 CoreData 0x00000001a469888c _executeGenerateObjectIDRequest + 388 (NSSQLCore_Functions.m:6021)
13 CoreData 0x00000001a46986a4 -[NSSQLGenerateObjectIDRequestContext executeRequestCore:] + 28 (NSSQLObjectIDRequestContext.m:42)
14 CoreData 0x00000001a45fb380 -[NSSQLStoreRequestContext executeRequestUsingConnection:] + 240 (NSSQLStoreRequestContext.m:183)
15 CoreData 0x00000001a45fb0a8 __52-[NSSQLDefaultConnectionManager handleStoreRequest:]_block_invoke + 60 (NSSQLConnectionManager.m:307)
16 CoreData 0x00000001a45fafe0 __37-[NSSQLiteConnection performAndWait:]_block_invoke + 48 (NSSQLiteConnection.m:755)
17 libdispatch.dylib 0x00000001a4357fa8 _dispatch_client_callout + 20 (object.m:576)
18 libdispatch.dylib 0x00000001a43677fc _dispatch_lane_barrier_sync_invoke_and_complete + 56 (queue.c:1104)
19 CoreData 0x00000001a45b5ba4 -[NSSQLiteConnection performAndWait:] + 176 (NSSQLiteConnection.m:752)
20 CoreData 0x00000001a45b5a68 -[NSSQLDefaultConnectionManager handleStoreRequest:] + 248 (NSSQLConnectionManager.m:302)
21 CoreData 0x00000001a45b5938 -[NSSQLCoreDispatchManager routeStoreRequest:] + 228 (NSSQLCoreDispatchManager.m:60)
22 CoreData 0x00000001a45b573c -[NSSQLCore dispatchRequest:withRetries:] + 172 (NSSQLCore.m:4044)
23 CoreData 0x00000001a46737b4 -[NSSQLCore _obtainPermanentIDsForObjects:withNotification:error:] + 1324 (NSSQLCore.m:2830)
24 CoreData 0x00000001a460ba98 -[NSSQLCore _prepareForExecuteRequest:withContext:error:] + 272 (NSSQLCore.m:2946)
25 CoreData 0x00000001a460a0f8 __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke.547 + 8988 (NSPersistentStoreCoordinator.m:2995)
26 CoreData 0x00000001a45d6660 -[NSPersistentStoreCoordinator _routeHeavyweightBlock:] + 264 (NSPersistentStoreCoordinator.m:668)
27 CoreData 0x00000001a45ded28 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 1200 (NSPersistentStoreCoordinator.m:2810)
28 CoreData 0x00000001a4655988 -[NSManagedObjectContext save:] + 984 (NSManagedObjectContext.m:1593)
29 CoreData 0x00000001a46f47dc __52+[NSCKEvent beginEventForRequest:withMonitor:error:]_block_invoke_2 + 352 (NSCKEvent.m:76)
30 CoreData 0x00000001a45c28f0 developerSubmittedBlockToNSManagedObjectContextPerform + 476 (NSManagedObjectContext.m:3984)
31 libdispatch.dylib 0x00000001a4357fa8 _dispatch_client_callout + 20 (object.m:576)
32 libdispatch.dylib 0x00000001a43677fc _dispatch_lane_barrier_sync_invoke_and_complete + 56 (queue.c:1104)
33 CoreData 0x00000001a4615c34 -[NSManagedObjectContext performBlockAndWait:] + 308 (NSManagedObjectContext.m:4108)
34 CoreData 0x00000001a46f45ac __52+[NSCKEvent beginEventForRequest:withMonitor:error:]_block_invoke + 192 (NSCKEvent.m:66)
35 CoreData 0x00000001a4825e68 -[PFCloudKitStoreMonitor performBlock:] + 92 (PFCloudKitStoreMonitor.m:148)
36 CoreData 0x00000001a46f4394 +[NSCKEvent beginEventForRequest:withMonitor:error:] + 256 (NSCKEvent.m:61)
37 CoreData 0x00000001a47cc6ec __57-[NSCloudKitMirroringDelegate _performExportWithRequest:]_block_invoke + 260 (NSCloudKitMirroringDelegate.m:1433)
38 CoreData 0x00000001a47c9970 __92-[NSCloudKitMirroringDelegate _openTransactionWithLabel:assertionLabel:andExecuteWorkBlock:]_block_invoke + 72 (NSCloudKitMirroringDelegate.m:957)
39 libdispatch.dylib 0x00000001a4356248 _dispatch_call_block_and_release + 32 (init.c:1549)
40 libdispatch.dylib 0x00000001a4357fa8 _dispatch_client_callout + 20 (object.m:576)
41 libdispatch.dylib 0x00000001a435f5cc _dispatch_lane_serial_drain + 768 (queue.c:3934)
42 libdispatch.dylib 0x00000001a4360158 _dispatch_lane_invoke + 432 (queue.c:4025)
43 libdispatch.dylib 0x00000001a436b38c _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:7193)
44 libdispatch.dylib 0x00000001a436abd8 _dispatch_workloop_worker_thread + 540 (queue.c:6787)
45 libsystem_pthread.dylib 0x0000000227213680 _pthread_wqthread + 288 (pthread.c:2696)
46 libsystem_pthread.dylib 0x0000000227211474 start_wqthread + 8 (:-1)
Hi everyone!
I have an app on the App Store that uses Core Data as its data store. (It's called Count on Me: Tally Counter. Feel free to check it out.) One of the app's core feature is an interactive widget with a simple button. When the button is tapped, it's supposed to update the entity in the store.
My requirement is that the changes are then reflected with minimal latency in the main app and – ideally – also on other devices of the same iCloud user. And vice-versa: When an entity is updated in the app (or on another device where the same iCloud user is logged in), the widget that shows this entity should also refresh to reflect the changes.
I have read multiple articles, downloaded sample projects, searched Stackoverflow and the Apple developer forums, and tried to squeeze a solution out of AI, but couldn't figure out how to make this work reliably.
So I tried to reduce the core problem to a minimal example project. It has two issues that I cannot resolve:
When I update an entity in the app, the widget is immediately updated as intended (due to a call to WidgetCenter's reloadAllTimelines method). However, when I update the same entity from the interactive widget using the same app intent, the changes are not reflected in the main app.
For the widget and the app to use the same local data store, I need to enable App Groups in both targets and set a custom location for the store within the shared app group. So I specify a custom URL for the NSPersistentStoreDescription when setting up the Core Data stack. The moment I do this, iCloud sync breaks.
Issue no. 1 is far more important to me as I haven't officially enabled iCloud sync yet in my real app that's already on the App Store. But it would be wonderful to resolve issue no. 2 as well. Surely, there must be a way to synchronize changes to the source of truth triggered by interactive widget with other devices of the same iCloud user. Otherwise, the feature to talk to the main app and the feature to synchronize with iCloud would be mutually exclusive.
Some other developers I talked to have suggested that the widget should only communicate proposed changes to the main app and once the main app is opened, it processes these changes and writes them to the NSPersistentCloudKitContainer which then synchronizes across devices. This is not an option for me as it would result in a stale state and potential data conflicts with different devices. For example, when a user has the same widget on their iPhone and their iPad, taps a button on the iPhone widget, that change would not be reflected on the iPad widget until the user decides to open the app on the iPhone. At the same time, the user could tap the button multiple times on their iPad widget, resulting in a conflicting state on both devices. Thus, this approach is not a viable solution.
An answer to this question will be greatly appreciated. The whole code including the setup of the Core Data stack is included in the repository reference above. Thank you!
I'm trying to set up an application using SwiftData to have a number of models backed by a local datastore that's not synced to CloudKit, and another set of models that is. I was able to achieve this previously with Core Data using multiple NSPersistentStoreDescription instances.
The set up code looks something like:
do {
let fullSchema = Schema([
UnsyncedModel.self,
SyncedModel.self,
])
let localSchema = Schema([UnsyncedModel.self])
let localConfig = ModelConfiguration(schema: localSchema, cloudKitDatabase: .none)
let remoteSchema = Schema([SyncedModel.self])
let remoteConfig = ModelConfiguration(schema: remoteSchema, cloudKitDatabase: .automatic)
container = try ModelContainer(for: fullSchema, configurations: localConfig, remoteConfig)
} catch {
fatalError("Failed to configure SwiftData container.")
}
However, it doesn't seem to work as expected. If I remove the synced/remote schema and configuration then everything works fine, but the moment I add in the remote schema and configuration I get various different application crashes. Some examples below:
A Core Data error occurred." UserInfo={Reason=Entity named:... not found for relationship named:...,
Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<...>
Has anyone ever been able to get a similar setup to work using SwiftData?
I have transitioned to CKSyncEngine for syncing data to iCloud, and it is working quite well. I have a question regarding best practices for modifying and saving a CKRecord which already exists in the private or shared database.
In my current app, most CKRecords will never be modified after saving to the database, so I do not persist a received record locally after updating my local data model. In the rare event that the local data for that record is modified, I manually fetch the associated server record from the database, modify it, and then use CKSyncEngine to save the modified record.
As an alternative method, I can create a new CKRecord locally with the corresponding recordID and the modified data, and then use CKSyncEngine to attempt to save that record to the database. Doing so generates an error in the delegate method handleSentRecordZoneChanges, where I receive the local record I tried to save back inevent.failedRecordSaves with a .serverRecordChanged error, along with the corresponding server CKRecord. I can then update that server record with the local data and re-save using CKSyncEngine. I have not yet seen any issues when doing it this way.
The advantage of the latter method is that CKSyncEngine handles the entire database operation, eliminating the manual fetch step. My question is: is this an acceptable practice, or could this result in other unforeseen issues?
Here’s the situation:
• You’re downloading a huge list of data from iCloud.
• You’re saving it one by one (sequentially) into SwiftData.
• You don’t want the SwiftUI view to refresh until all the data is imported.
• After all the import is finished, SwiftUI should show the new data.
The Problem
If you insert into the same ModelContext that SwiftUI’s @Environment(.modelContext) is watching, each insert may cause SwiftUI to start reloading immediately.
That will make the UI feel slow, and glitchy, because SwiftUI will keep trying to re-render while you’re still importing.
How to achieve this in Swift Data ?