paulw11 / Seam3

Cloudkit based persistent store for Core Data
Other
209 stars 25 forks source link

SMStore.predicate does not work with NSFetchedResultsController #54

Closed DJ-Glock closed 7 years ago

DJ-Glock commented 7 years ago

Related to this issue.

Hello Paul. I have tried to use your functions with NSFetchedResultsController and looks like it does not work.

Here is my code:

class TableSessionsTableViewController: FetchedResultsTableViewController {

    //MARK: Variables
    var currentTable: TablesTable? = nil
    fileprivate var currentTableSession: TableSessionTable?
    fileprivate var fetchedResultsController: NSFetchedResultsController<TableSessionTable>?
    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    //MARK: functions for table update
    private func updateTableView () {
        let context = AppDelegate.viewContext
        let predicate = appDelegate.smStore?.predicate(for: "table", referencing: currentTable!)
        let originalPredicate = NSPredicate(format: "table = %@ and closeTime <> %@", currentTable!, NSNull() as CVarArg)

        let request : NSFetchRequest<TableSessionTable> = TableSessionTable.fetchRequest()
        request.predicate = predicate
        request.sortDescriptors = [NSSortDescriptor(key: "openTime", ascending: false, selector: #selector(NSDate.compare(_:)))]
        fetchedResultsController = NSFetchedResultsController<TableSessionTable>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "truncatedOpenDate", cacheName: nil)
        fetchedResultsController?.delegate = self
        let simpleFetch = try? context.fetch(request)
        print(simpleFetch?.count)
        try? fetchedResultsController?.performFetch() // fails here
        tableView.reloadData()
    }

Simple fetch request works with no issues:

let simpleFetch = try? context.fetch(request)
print(simpleFetch?.count)

gives me Optional(84)

But try? fetchedResultsController?.performFetch() fails with: libc++abi.dylib: terminating with uncaught exception of type NSException

Any thoughts how to fix this?

2017-10-27 16 19 00
DJ-Glock commented 7 years ago

It looks like the issue is here: fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "truncatedOpenDate", cacheName: nil)

If it's nil - it works properly. But I used truncatedOpenDate. It's variable from my TableSessionTable class:

class TableSessionTable: NSManagedObject {
    //MARK: variables
    static let context = AppDelegate.viewContext
    static let tableSessionRequest: NSFetchRequest<TableSessionTable> = TableSessionTable.fetchRequest()

    var truncatedOpenDate: String? {
        return openTime!.getTimeStrWithDayPrecision()
    }

...

extension NSDate {
    //Function to return date in string format for sections
    func getTimeStrWithDayPrecision() -> String {
        let formatter = DateFormatter()
        formatter.timeStyle = .none
        formatter.dateStyle = .short
        formatter.doesRelativeDateFormatting = true
        return formatter.string(from: self as Date)
    }
}

...

It's sad.

DJ-Glock commented 7 years ago

And I have another bug with using NSFetchedResultsController.

I have this structure: UITableView with NSFetchedResultsController. UIView with some data that is being observed by NSFetchedResultsController. When I try to change something, application crashes with error like this:

CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. [<CafeManager.TablesTable 0x60000028c620> valueForUndefinedKey:]: the entity TablesTable is not key value coding-compliant for the key "sm_LocalStore_RecordID". with userInfo { NSTargetObjectUserInfoKey = "<CafeManager.TablesTable: 0x60000028c620> (entity: TablesTable; id: 0x60000022ffe0 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TablesTable/pC5E02D98-3047-40DC-A7A1-99E7A9D59DE0> ; data: {\n tableCapacity = 6;\n tableDescription = nil;\n tableName = \"Table 1\";\n tableSession = \"<relationship fault: 0x60400062b920 'tableSession'>\";\n})"; NSUnknownUserInfoKey = "sm_LocalStore_RecordID"; } libc++abi.dylib: terminating with uncaught exception of type NSException

As per StackOverflow it's a bug caused by NSFetchedResultsController that listens to changes in context. Not sure if it can be fixed with Seam3.

paulw11 commented 7 years ago

You need to flag your truncatedOpenDate as @objc otherwise you will get a the "not key value compliant" exception.

i.e.:

class TableSessionTable: NSManagedObject {
    //MARK: variables
    static let context = AppDelegate.viewContext
    static let tableSessionRequest: NSFetchRequest<TableSessionTable> = TableSessionTable.fetchRequest()

    @objc var truncatedOpenDate: String? {
        return openTime!.getTimeStrWithDayPrecision()
    }
paulw11 commented 7 years ago

Can you show more code for the second problem? The NSManagedObject that is visible to your code should not have a sm_LocalStore_RecordID. This crash indicates that somehow your code has visibility of the record that is used inside Seam3 for the local backing store.

paulw11 commented 7 years ago

Have a look at this code; https://github.com/paulw11/migrateSample - It is a minimum example I put together when I was working on a bug with Apple.

You will note that the code does not create the Seam3 store explicitly; it merely provides a URL and store type to migratePersistentStore - the migration process will create the new store.

Once you have a reference to your "old" persistent store this is all you need to do:

let newURL = documentDirectory.appendingPathComponent("new.sqlite")

do {
    SMStore.registerStoreClass()
    SMStore.syncAutomatically = false
    try psc.migratePersistentStore(oldStore, to: newURL, options:nil, withType:SMStore.type)
} catch {
    fatalError("Failed to migrate store: \(error)")
}
DJ-Glock commented 7 years ago

@paulw11 As I mentioned in #56 , I have implemented migration according to your example, but issue has not been resolved.

didFinishLaunchingWithOptions
Not yet migrated, migrating to file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/F17F412A-5DCB-40C2-B1CD-7BDDA8EE1EFB/data/Containers/Data/Application/2C0BBA57-6F2B-44CA-AB58-4279459E245C/Documents/CafeManagerSeam3.sqlite
Default store is located here: file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/F17F412A-5DCB-40C2-B1CD-7BDDA8EE1EFB/data/Containers/Data/Application/2C0BBA57-6F2B-44CA-AB58-4279459E245C/Library/Application%20Support/CafeManager.sqlite
Store is SMStore

Sync Started
No more records coming
Sync Performed
Sync performed successfully
CoreData: error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  [<CafeManager.TablesTable 0x604000a86f90> valueForUndefinedKey:]: the entity TablesTable is not key value coding-compliant for the key "sm_LocalStore_RecordID". with userInfo {
    NSTargetObjectUserInfoKey = "<CafeManager.TablesTable: 0x604000a86f90> (entity: TablesTable; id: 0x604000c2e220 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TablesTable/pE045910B-E3A5-4DD5-9064-7B8C054566CF> ; data: {\n    tableCapacity = 4;\n    tableDescription = nil;\n    tableName = \"\\U0421\\U0442\\U043e\\U043b\\U0438\\U043a 3\";\n    tableSession =     (\n        \"0x60c000e32560 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/p7C835387-C895-497A-9FED-649499BB38B5>\",\n        \"0x60c000e320e0 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/p15F97C92-D478-43D4-A569-5C9168D35895>\",\n        \"0x60c000e32280 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/p7F3ED50D-0CF0-4678-AADC-2A60D1FC5370>\",\n        \"0x60c000e32380 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/pCB138E22-08DD-4216-82DB-C3A492F517C3>\",\n        \"0x60c000e32480 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/pE4FE9DB7-9D5D-4235-993B-7487D1509B1F>\",\n        \"0x60c000e32580 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/pC7BD5D64-9156-47DA-A718-062F4BE7A774>\",\n        \"0x60c000e32120 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/pB8FCE396-BA4F-44B6-9737-3562AEE3467A>\",\n        \"0x60c000e32060 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/p0FC2BB0F-5C37-43B9-B9AF-A00F1BBA0A62>\",\n        \"0x60c000e322a0 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/p1FB369E0-A7D3-445E-8DF9-D3165A100C83>\",\n        \"0x60c000e323a0 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/TableSessionTable/pDEF253A2-11CD-4675-B70A-66F902ADBB4F>\",\n        \"(...and 33 more...)\"\n    );\n})";
    NSUnknownUserInfoKey = "sm_LocalStore_RecordID";
}
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 
DJ-Glock commented 7 years ago

Hello Paul! I have created dummy project for you and replayed the issue.

Just download it and run. Click "Fill database" button, it will insert some test data. Click "Open table", after table appears, click on any cell. Click "Refresh date and save" and you will get an error:

file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/F17F412A-5DCB-40C2-B1CD-7BDDA8EE1EFB/data/Containers/Data/Application/22DE9B16-E1D7-4A9C-B609-2E7C455E5F73/Documents/Seam3Example.sqlite
2017-11-17 01:10:46.759418+0300 Seam3Example[5043:260012] [Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
2017-11-17 01:11:08.893829+0300 Seam3Example[5043:260012] [error] error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  [<Seam3Example.CategoryTable 0x60c00028c030> valueForUndefinedKey:]: the entity CategoryTable is not key value coding-compliant for the key "sm_LocalStore_RecordID". with userInfo {
    NSTargetObjectUserInfoKey = "<Seam3Example.CategoryTable: 0x60c00028c030> (entity: CategoryTable; id: 0x608000429a20 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/CategoryTable/p1BDEC192-6911-429E-971E-AB840EEE5D7C> ; data: {\n    categoryName = Public;\n    event = \"<relationship fault: 0x6080004286a0 'event'>\";\n})";
    NSUnknownUserInfoKey = "sm_LocalStore_RecordID";
}
CoreData: error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  [<Seam3Example.CategoryTable 0x60c00028c030> valueForUndefinedKey:]: the entity CategoryTable is not key value coding-compliant for the key "sm_LocalStore_RecordID". with userInfo {
    NSTargetObjectUserInfoKey = "<Seam3Example.CategoryTable: 0x60c00028c030> (entity: CategoryTable; id: 0x608000429a20 <x-coredata://A9909604-1EF0-4049-BD7F-2CF6AE3D3A6D/CategoryTable/p1BDEC192-6911-429E-971E-AB840EEE5D7C> ; data: {\n    categoryName = Public;\n    event = \"<relationship fault: 0x6080004286a0 'event'>\";\n})";
    NSUnknownUserInfoKey = "sm_LocalStore_RecordID";
}
2017-11-17 01:11:08.899742+0300 Seam3Example[5043:260012] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Seam3Example.CategoryTable 0x60c00028c030> valueForUndefinedKey:]: the entity CategoryTable is not key value coding-compliant for the key "sm_LocalStore_RecordID".'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000110ded1cb __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x0000000110208f41 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000110ded119 -[NSException raise] + 9

https://www.dropbox.com/s/44grprfs5tspjej/Seam3Example.zip?dl=0 Here you are the link to zipped project.

As I can see, issue happens only when SMStore.predicate is used: let referencePredicate = appDelegate.smStore?.predicate(for: "table", referencing: currentTable!)

And caused by observers from FetchedResultsTableViewController class.

paulw11 commented 7 years ago

Thanks for this. It will help. I have been a bit snowed under with my day job, but I will try and find some time to look at this for you.

paulw11 commented 7 years ago

I have just published 1.4.4 which will hopefully address this issue; It certainly fixed the sample you provided. Be aware that the predicate functions now return an optional so you will need to unwrap.

DJ-Glock commented 7 years ago

It works fine now. Thanks a lot, Paul.

Jerland2 commented 6 years ago

@paulw11 @DJ-Glock After using the SMStore predicate method does your Fetched Result Controller's delegate still work? After using the method my delegate methods no longer get triggered. Could you share your configuration, or if it works for you?

DJ-Glock commented 6 years ago

Hi Unfortunately I cannot answer for sure. I also had an impression that observers do not work properly with predicate method from Seam. SHould be investigated and fixed I believe.

вт, 26 дек. 2017 г. в 1:42, Joey notifications@github.com:

@DJ-Glock https://github.com/dj-glock After using the predicate method does your Fetched Result Controller's delegate still work? After using the method my delegate methods no longer get triggered. Could you share your configuration, or if it works for you?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/paulw11/Seam3/issues/54#issuecomment-353893960, or mute the thread https://github.com/notifications/unsubscribe-auth/AZSHV34Xfor0axJS9tTCQp_Vjv8iHfmXks5tECTIgaJpZM4QJDjL .

Jerland2 commented 6 years ago

@paulw11 I hope it can be fixed, it’s the last thing stopping me from using seam3 and shipping the iCloud feature! I have opened an issue dedicated to it along with a linked example displaying the problem and steps to reproduce.