1 Reply
      Latest reply on Aug 21, 2019 2:02 PM by grysvn
      grysvn Level 1 Level 1 (0 points)

        I'm trying to get an example project using CoreData and QueryGenerationTokens working. The essence of the project is to be committing changes to a background context on a timer (emulating changes coming down from a server) that shouldn't be displayed until an action is taken on the UI (say, a button press).

         

         

        Currently, I have changes being saved on the background context (an entity is being added every 5s and saved) and they are automatically coming into the view context (as expected, .automaticallyMergesChangesFromParent is set to true). Where things go wrong, I am pinning the view context before any of these changes happen to the current query generation token. I would expect the view to not update with the background items being added, but it is updating with them. So it seems the query generation tokens are having no effect?

         

         

        Some of the possible issues I've thought of:

         

         

        - the only example (https://developer.apple.com/library/archive/releasenotes/General/WhatNewCoreData2016/ReleaseNotes.html#//apple_ref/doc/uid/TP40017342-CH1-DontLinkElementID_3) I've found from Apple doesn't show them using it with a fetched results controller (I'm using `@FetchRequest` in SwiftUI, which I'm almost entirely certain is essentially the same), so that may have an effect?

        - .automaticallyMergeChangesFromParent shouldn't be used and I should try a merge policy, but that doesn't seem to work either and conceptually, it seems the query generation tokens should work with this and pin to the generation no matter the merging.

         

         

        Code for view - handles loading data from view context

         

         

        // Environment object before fetch request necessary
        // Passed in wherever main view is instantiated through .environment()
        @Environment(\.managedObjectContext) var managedObjectContext
        
        
        // Acts as fetched results controller, loading data automatically into items upon the managedObjectContext updating
        // ExampleCoreDataEntity.retrieveItemsFetchRequest() is an extension method on the entity to easily get a fetch request for the type with sorting
        @FetchRequest(fetchRequest: ExampleCoreDataEntity.retrieveItemsFetchRequest()) var items: FetchedResults
        
        
        var body: some View {
            NavigationView {
                // Button to refresh and bring in changes
                Button(
                    action: {
                        do {
                            try self.managedObjectContext.setQueryGenerationFrom(.current)
                            self.managedObjectContext.refreshAllObjects()
                        } catch {
                            print(error.localizedDescription)
                        }
                    },
                    label: { Image(systemName: "arrow.clockwise") }
                )
        
        
                // Creates a table of items sorted by the entity itself (entities conform to Hashable)
                List(self.items, id: \.self) { item in
                    Text(item.name ?? "")
                }
            }
        }

         

        Code in SceneDelegate (where a SwiftUI application starts up) where I also initialize what is needed for CoreData:

         

        // Setup and pass in environment of managed object context to main view
        // via extension on persistent container that sets up CoreData stack
        let managedObjectContext = NSPersistentContainer.shared.viewContext
        do {
            try managedObjectContext.setQueryGenerationFrom(.current)
        } catch {
            print(error.localizedDescription)
        }
        let view = MainView().environment(\.managedObjectContext, managedObjectContext)
        
        
        // Setup background adding
        timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(backgroundCode), userInfo: nil, repeats: true)
        
        
        // Setup window and pass in main view
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: view)

         

         

        Function adding data in the background:

        @objc func backgroundCode() {
            ExampleCoreDataEntity.create(names: ["background object"], in: backgroundContext, shouldSave: true)
        }  

         

        Setup of NSPersistentContainer:
        extension NSPersistentContainer {
            private struct SharedContainerStorage {
                static let container: NSPersistentContainer = {
                    let container = NSPersistentContainer(name: "Core_Data_Exploration")
                    container.loadPersistentStores { (description, error) in
                        guard error == nil else {
                            assertionFailure("CoreData: Unresolved error \(error!.localizedDescription)")
                            return
                        }
                        container.viewContext.automaticallyMergesChangesFromParent = true
                    }
                    return container
                }()
            }
        
        
            static var shared: NSPersistentContainer {
                return SharedContainerStorage.container
            }
        }

         

         

        Create/Read/Update/Delete functions on the entity:

        extension ExampleCoreDataEntity {
            static func retrieveItemsFetchRequest() -> NSFetchRequest {
                let request: NSFetchRequest = ExampleCoreDataEntity.fetchRequest()
                request.sortDescriptors = [NSSortDescriptor(keyPath: \ExampleCoreDataEntity.creationDate, ascending: false)]
                return request
            }
           
            static func create(names: [String], in context: NSManagedObjectContext, shouldSave save: Bool = false) {
                context.perform {
                    names.forEach { name in
                        let item = ExampleCoreDataEntity(context: context)
                        item.name = name
                        item.creationDate = Date()
                        item.identifier = UUID()
                    }
        
        
                    do {
                        if save {
                            try context.save()
                        }
                    } catch {
                        // print error
                    }
                }
            }
           
            func delete(in context: NSManagedObjectContext, shouldSave save: Bool = false) {
                context.perform {
                    let name = self.name ?? "an item"
        
        
                    context.delete(context.object(with: self.objectID))
                    do {
                        if save {
                            try context.save()
                        }
                    } catch {
                        // print error
                    }
                }
            }
        }
        • Re: How to properly use query generation tokens?
          grysvn Level 1 Level 1 (0 points)

          Figured it out - the main issue is that .automaticallyMergesChangesFromParent being set to true does not work with query generation tokens. This is not in the formal documentation, it is in the header file of NSManagedObjectContext documented above the .automaticallyMergesChangesFromParent property.