I am having problems when I first loads the app. The time it takes for the Items to be sync from my CloudKit to my local CoreData is too long.
Code
I have the model below defined by my CoreData.
public extension Item {
@nonobjc class func fetchRequest() -> NSFetchRequest<Item> {
NSFetchRequest<Item>(entityName: "Item")
}
@NSManaged var createdAt: Date?
@NSManaged var id: UUID?
@NSManaged var image: Data?
@NSManaged var usdz: Data?
@NSManaged var characteristics: NSSet?
@NSManaged var parent: SomeParent?
}
- image and usdz columns are both marked as
BinaryData
and AttributeAllows External Storage
is also selected.
I made a Few tests loading the data when the app is downloaded for the first time. I am loading on my view using the below code:
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.createdAt, ascending: true)]
)
private var items: FetchedResults<Item>
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 40) {
ForEach(items, id: \.self) { item in
Text(item.id)
}
}
}
}
}
Test 1 - Just loads everything
When I have on my cloudKit images and usdz a total of 100mb data, it takes around 140 seconds to show some data on my view (Not all items were sync, that takes much longer time)
Test 2 - Trying getting only 10 items at the time ()
This takes the same amount of times the long one . I have added the following in my class, and removed the @FetchRequest
:
@State private var items: [Item] = [] // CK
@State private var isLoading = false
@MainActor
func loadMoreData() {
guard !isLoading else { return }
isLoading = true
let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.predicate = NSPredicate(format: "title != nil AND title != ''")
fetchRequest.fetchLimit = 10
fetchRequest.fetchOffset = items.count
fetchRequest.predicate = getPredicate()
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.createdAt, ascending: true)]
do {
let newItems = try viewContext.fetch(fetchRequest)
DispatchQueue.main.async {
items.append(contentsOf: newItems)
isLoading = false
}
} catch {}
}
Test 2 - Remove all images and usdz from CloudKit set all as Null
Setting all items BinaryData to null, it takes around 8 seconds to Show the list.
So as we can see here, all the solutions that I found are bad. I just wanna go to my CloudKit and fetch the data with my CoreData. And if possible to NOT fetch all the data because that would be not possible (imagine the future with 10 or 20GB or data) What is the solution for this loading problem? What do I need to do/fix in order to load lets say 10 items first, then later on the other items and let the user have a seamlessly experience?
Questions
- What are the solutions I have when the user first loads the app?
- How to force CoreData to query directly cloudKit?
- Does CoreData + CloudKit + NSPersistentCloudKitContainer will download the whole CloudKit database in my local, is that good????
- Storing images as
BinaryData
withAllow external Storage
does not seems to be working well, because it is downloading the image even without the need for the image right now, how should I store the Binary data or Images in this case?
I'd firstly point you the following technotes, which I believe will give you the whole picture about how Core Data + CloudKit works:
-
TN3163: Understanding the synchronization of NSPersistentCloudKitContainer
-
TN3164: Debugging the synchronization of NSPersistentCloudKitContainer
Concretely to your questions:
What are the solutions I have when the user first loads the app? How to force CoreData to query directly cloudKit?
There is no API for an app to speed up the initial synchronization as of today. If there is a lot of data on the server, the synchronization will indeed take long time. I'd suggest that you file a feedback report against that.
There is no API to force CoreData to import from CloudKit, and that is as-designed, as discussed in the technotes mentioned above.
Does CoreData + CloudKit + NSPersistentCloudKitContainer will download the whole CloudKit database in my local, is that good????
When using NSPersistentCloudKitContainer
, there are indeed two copies of the data – One is local on the device and the other is on the CloudKit server. CloudKit doesn't provide any local cache, and so the local copy is necessary to avoid querying CloudKit server every time.
If your question is why NSPersistentCloudKitContainer
doesn't import the data lazily, or only the subset the app instance needs, it is that the API wasn't designed to work that way. Also, whether lazily loading is better or not will be quite arguable.
Storing images as BinaryData with Allow external Storage does not seems to be working well, because it is downloading the image even without the need for the image right now, how should I store the Binary data or Images in this case?
NSPersistentCloudKitContainer
automatically synchronizes the whole database anyway, and there is no API to change the behavior as of today.
In any case, if you believe that CoreData + CloudKit should be improved in some way, please feel free to file a feedback report with your use case and reasoning.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.