I did some further testing using only a manual fetch (which gets executed only once). It seems it's just the fetch request (for only 12000 entities) itself which is horribly slow. Nevertheless, I wonder why I should get this issue only for macOS and not iOS given it's the same code?
==========================================
I am developing a simple SwiftUI app for macOS Big Sur/iOS 14 which synchronizes "Card"-entities (nothing fancy; around 20 properties and 7 relationships) via CloudKit. Everything works fine, however, after importing (only) 12000 entries (from a decoded JSON file), the macOS app (but not the iOS app) became unusable, i.e. freezes/has a very high CPU usage. Strangely, this issue also occurs if no data is shown on the UI, e.g. with the following simple view:
struct MainView: View {
@ObservedObject var model: MainViewModel
@Environment(\.colorScheme) var colorScheme
var body: some View {
CardList(predicate: Card.predicate(searchText: self.model.searchText,
pinned: self.model.filterPinned,
priority: self.model.getPrioritiesAsArray(),
subjects: self.model.selectedSubjects,
bundles: self.model.selectedBundles),
sortDescriptor: EntrySort(sortType: self.model.sortType, sortOrder:self.model.sortOrder).sortDescriptor,
model: self.model)
}
}
struct CardList: View {
@ObservedObject var model: MainViewModel
@Environment(\.colorScheme) var colorScheme
@Environment(\.managedObjectContext)
var context: NSManagedObjectContext
@FetchRequest(
entity: Card.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Card.id, ascending: false)
]
)
private var result: FetchedResults<Card>
init(predicate: NSPredicate?,
sortDescriptor: NSSortDescriptor,
model: MainViewModel) {
let fetchRequest = NSFetchRequest<Card>(entityName: "Card")
fetchRequest.sortDescriptors = [sortDescriptor]
if let predicate = predicate {
fetchRequest.predicate = predicate
}
_result = FetchRequest(fetchRequest: fetchRequest)
self.model = model
}
var body: some View {
Text("TEST")
}
}
The "MainViewModel" look as follows (abbreviated for better legibility):
class MainViewModel: ObservableObject {
@Published var searchText: String = ""
@Published var selectedSubjects: [Subject]?
@Published var selectedBundles: [Bundle]?
@Published var selectedTags: [Tag]?
@Published var filterPinned: Bool? = nil
@Published var filterPriorityVeryHigh: Bool = false
@Published var filterPriorityHigh: Bool = false
@Published var filterPriorityNormal: Bool = false
@Published var filterPriorityLow: Bool = false
func getPrioritiesAsArray() -> [CardPriority] {
var res: [CardPriority] = []
if self.filterPriorityLow { res.append(CardPriority.Low)}
if self.filterPriorityNormal { res.append(CardPriority.Normal)}
if self.filterPriorityHigh { res.append(CardPriority.High)}
if self.filterPriorityVeryHigh { res.append(CardPriority.VeryHigh)}
return res
}
@Published var sortType = SortType.dateCreated
@Published var sortOrder = SortOrder.ascending
}
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var coreDataStack = CoreDataStack(modelName: "[Name of Model]")
var mainViewModel = MainViewModel()
func applicationDidFinishLaunching(_ aNotification: Notification) {
coreDataStack.viewContext.automaticallyMergesChangesFromParent = true
let mainView = MainView(model: mainViewModel)
.environment(\.managedObjectContext, coreDataStack.viewContext)
// ...
}
The very same data was successfully synchronized to the iOS version of the app and can be displayed there without any performance issues (using the same controls, namely a LazyVStack). However, the macOS app became totally unusable because of the constant high CPU usage. Also, I cannot see anything that would constantly change a variable and thereby trigger a constant refresh of the displayed entities (i.e. trigger a new fetch request).
Does anybody have an idea why this issue occurs?
Thanks in advance for any advise!
Sebastian
※ One thing I noticed is that Xcode floods the console with the warning "NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release" because I am still using some [String]-Transformable-Properties on the Card entity. I will address this issue later, but since this warning seems to be displayed constantly, I wonder if @FetchRequest constantly fetches/evaluates all entities? Further, I assume the vast amount of warnings displayed should only have an influence on Xcode's CPU usage?
I solved this issue; it seems that it was indeed the warning "'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release" which caused this spike in CPU usage. After setting "NSSecureUnarchiveFromDataTransformer" to all [String]-Transformable-Properties of all database entities, the issue was gone.