NSFetchResults update delegate works first time, crashes second time - core-data

I've looked around SO for similar answers, but my issue seems a litte different.
I have a UITableView that is tied to a NSFetchResultsController. The goal is to pull up some data, add a couple rows into the Context, and the table is automatically updated. Simple, right?
init -> empty table -> performFetch -> create some objects in the Context -> delegate sees this and updates my table.
I'm using the boilerplate NSFetchResultsController for noticing when the current context has been modified.
When I run this with a clean Simulator/iOS platform, the NSFetchController successfully recognizes that data in the Context has been updated. But if I run the app a second time, I get the following error:
CoreData: error: Serious application error. Exception was caught during Core Data change
processing. This is usually a bug within an observer of
NSManagedObjectContextObjectsDidChangeNotification. *** -[__NSArrayI objectAtIndex:]:
index 40 beyond bounds for empty array with userInfo (null)
The crash occurs on calling [self.tableView beginUpdates];
In my debugging I can see that '[fetchedResultsController fetchedObjects]' is completely empty and I think thats the problem - shouldn't this be updating with my test data since I modified the context? I'm using the Apple Recipe and CoreDataBooks examples as reference.

I think this is because you Data Modle in class just not fit the entity in you .xcdatamodeld file.

Related

NSFetchedResultsController + UICollectionViewDiffableDataSource + CoreData - How to diff on the entire object?

I'm trying to use some of the new diffing classes built into iOS 13 along with Core Data. The problem I am running into is that controllerdidChangeContentWith doesn't work as expected. It passes me a snapshot reference, which is a reference to a
NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>
meaning I get a list of sections/Object ID's that have changed.
This part works wonderfully. But the problem comes when you get to the diffing in the collection view. In the WWDC video they happily call
dataSource.apply(snapshot, animatingDifferences: true)
and everything works magically, but that is not the case in the actual API.
In my initial attempt, I tried this:
resolvedSnapshot.appendItems(snapshot.itemIdentifiersInSection(withIdentifier: section).map {
controller.managedObjectContext.object(with: $0 as! NSManagedObjectID) as! Activity
}, toSection: .all)
And this works for populating the cells, but if data is changed on a cell (IE. the cell title) the specific cell is never reloaded. I took a look at the snapshot and it appears the issue is simply that I have references to these activity objects, so they are both getting updated simultaneously (Meaning the activity in the old snapshot is equivalent to the one in the new snapshot, so the hashes are equal.)
My current solution is using a struct that contains all my Activity class variables, but that disconnects it from CoreData. So my data source became:
var dataSource: UICollectionViewDiffableDataSource<Section, ActivityStruct>
That way the snapshot actually gets two different values, because it has two different objects to compare. This works, but it seems far from elegant, is this how we were meant to use this? Or is it just in a broken state right now? The WWDC video seems to imply it shouldn't require all this extra boilerplate.
I ran into the same issue and I think I figured out what works:
There are two classes: UICollectionViewDiffableDataSource and UICollectionViewDiffableDataSourceReference
From what I can tell, when you use the first, you're taking ownership as the "Source of Truth" so you create an object that acts as the data source. When you use the second (the data source reference), you defer the "Source of Truth" to another data source (in this case, CoreData).
You would instantiate a ...DataSourceReference essentially the same way as a ...DataSource:
dataSourceReference = UICollectionViewDiffableDataSourceReference(collectionView: collectionView, cellProvider: { (collectionView, indexPath, object) -> UICollectionViewCell? in
let identifier = <#cell identifier#>
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
<#cell configuration#>
return cell
})
And then later when you implement the NSFetchedResultsControllerDelegate, you can use the following method:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)
{
dataSourceReference.applySnapshot(snapshot, animatingDifferences: true)
}
I watched the WWDC video as well and didn't see this referenced. Had to make a few mistakes to get here. I hope it works for you!

Promotion Not Opening from HMC: de.hybris.platform.jalo.JaloSystemException: Cannot create Jalo instance for item

When I try to open promotions on HMC it throw this error,
first Couldn't understand problem and cause of problem?
Try to clear cookies, login/logout, restart browser as well -looks nothing works. (check this link for more detail, can't find anything
https://answers.sap.com/questions/12761785/promotion-not-opening-from-hmc.html )
Try to check this: ( https://answers.sap.com/questions/12750795/cannot-create-jalo-instance-for-item-due-to-null-d.html?childToView=12798465#answer-12798465 ) clearing orphan type helps sometime.
If any one can guide how to solve this issue and explain cause of this issue.
Promotion Not Opening from HMC: Unhandled Exception:
de.hybris.platform.jalo.JaloSystemException: Cannot create Jalo
instance for item 000000 due to null
JaloSystemException is the mother of all Runtime exceptions in Hybris. So, the exception alone is not very informative.
The following comment i.e.
Cannot create Jalo instance for item
suggests that Hybris is trying to create an instance of a custom type, the definition of which (in items.xml) does not exist anymore. Either the definitions were removed manually or they were lost during system migration.
Ideally, deleting orphans should resolve this.
If you are still getting the error after removing the orphan types then -
Either there is some legacy code that is trying to create an instance of the custom types, most probably through a factory, otherwise, it would have given a compilation error during build itself.
The deleted custom type had a relation with the existing types, probably with the Promotion.
The ultimate solution is to run initialize on your local.
**Be very careful while running initialize as it will remove all the data from your system.

NSManagedObject stops receiving merged changes after obtainPermanentIDsForObjects

I have a hierarchy of managed object contexts, like so:
A
/ \
B C
...where A's parent is the persistent store. A and C use a private queue, and B uses the main queue. I've found that doesn't seem to matter, but I'll note it anyway.
So, here is the best way to replicate the issue:
- Context B creates an object. Because I need to reference this same object multiple places, I obtain a permanent object ID via obtainPermanentIDsForObjects. I then save.
- Context A inherits the change, saves it out to the permanent store. Context C sees the change notification and merges the changes in. All is good so far.
- I grab the object from context C and change one of the properties. I then save. Context A inherits the change and saves it to disk.
This is where it gets weird. Context B never sees the change. There is a change notification I intercept like so:
-(void)contextDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *context = [notification object];
if(context!=_moc.parentContext)
return;
NSLog(#"Context %# did save, merging into %#, info:\n%#", context, _moc, [notification userInfo]);
[_moc mergeChangesFromContextDidSaveNotification:notification];
}
And I see the change go out, and seeing it merged in to context B. But the object I created in the first step never sees any changes.
I've traced this all back to one very specific thing. If I don't obtain a permanent object ID on the object in the very first step, the changes get merged back to the original object in context B just fine. But this leaves me without a reliable object ID to use in other contexts (as far as I know).
This seems like it could be a bug. I hope not. But for some reason when I obtain a permanent object ID it blocks changes from being merged back in from other contexts.
Has anyone else seen this behavior? Any solutions?
(I know changes from context C are making it in to A, and A is saving them to disk. If I relaunch the app the changes are there from disk.)

NSUnknownKeyException for existing and non-mistyped key

I'm getting the following output:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[<NSManagedObject 0x21016610> setValue:forUndefinedKey:]: the entity MyEntity is
not key value coding-compliant for the key "aBooleanKey".'
The code that runs before I get the output is the following:
self.name = [managedObject valueForKey:#"name"];
self.language = [managedObject valueForKey:#"language"];
self.ownerID = [managedObject valueForKey:#"ownerID"];
// the following line is the scope of the problem:
self.aBooleanKey = [[managedObject valueForKey:#"aBooleanKey"] boolValue];
For me this looks like a mistyping or something, since all other entity attributes don't cause any problem. But I checked like twelve times and can't find any mistyping or so. It all looks just right. And a week ago it all worked just fine – I didn't change anything here.
Also there are a few other places where I do things with the managedObject and likewise all other attributes work just fine except this one. I tried deleting it in the Core Data store and retyping it, but it didn't solve the problem.
Are there any other reasons for this error?
The problem is quite strange (sometimes works, sometimes not), so I would suggest to change the name of the attribute (e.g. booleanStatus) and verify if the problem still persists or not.
Since you have changed the model you need to remove the app and ricreate it. Alternatively you could perform an automatic migration on Core Data. As you prefer.
Hope it helps.

Core Data NSManagedObject Insert and Save Methods

Is there an easier way to do something like the following in Core Data:
Entry *entry = [[Entry alloc] init];
entry.name = #"An Entry";
[entry save];
I realize you don't have to allocate an NSManagedObject, have to insert directly into the context like the following:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:[self managedObjectContext]];
But this is a lot of code. Also I would like to save by just messaging rather than have to save the entire context:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Could I put these in an NSManagedObject abstract class and have my managed objects extend that abstract class? Basically, I'm just trying to encapsulate more in my models and write less code in my controllers. Any help appreciated.
You can define your own subclass of NSManagedObject, and set all of your entities to use it.
The subclass can have whatever initialiser/save patterns you define, so long as it calls the proper parent class's initialiser.
You could have a +entity method, which might link to a statically defined context (this will restrict you to a single managed object context of course, but that's not always bad as long as you can also call the more primitive initialisers when you need a second context).
You might even have a +entityWithName: method.
As for saving the context, once again you can always define a subclass and add a simple -save method which saves the context and throws an exception if the save fails. You may choose to do this with a category extending the NSManagedObject class, instead of a subclass.
Note it is impossible to save just the change you made to that one entry object. You can only save all changes to an entire managed object context. If you need to save a single record, then you need to create a temporary managed object context, make a single change in it, then save the temporary context, and then sync the temporary context change over to all other managed object context's that currently exist in the app (there is an API to do make this complicated process relatively easy).
I don't like the code you posted to save the context, for several reasons:
Don't define a managedObjectContext variable that just points to self.managedObjectContext. In almost all situations that's an extra line of code for no benefit. At best you're making your code hard to read, at worst you might be introducing bugs.
Why are you checking if it is nil? Usually you should design your code so that it cannot ever be nil. Do the nil check in the constructor of your object, and if it's nil that is a critical failure. Your whole app is completely useless for the user, and you should make it clear to the user that they can't use the app by doing something drastic, such as a crash (an error alert first would be nice, but I wouldn't bother. I'd just throw an exception).
Why are you doing a check for hasChanges? I cannot think of many situations where you would need to do this check. Perhaps your app is allowing a user to make many changes, and then saving them several minutes later? This is bad. The context should be changed milliseconds after a group of changes are made, or else you're risking data loss. Your app could crash, the phone could run out of battery, or the user might receive a phone call and your app is consuming enough RAM that the OS will terminate it instantly in order to present the "incoming call" screen. You shouldn't need to check for hasChanges because you always perform a save operation immediately after making some changes.
As I kind of mentioned before, if the save fails you should present an error to the user then throw an exception. Avoid using NSLog() in deployment code, it's really only useful for development and beta builds.
Check out
NSManagedObject+ActiveRecord.h
Inside Restkit : http://restkit.org/
It is based on :
https://github.com/magicalpanda/MagicalRecord
I am using in RestKit app, but you can adopt it quite easily.
Good luck

Resources