In a subclass of NSManagedObject my overridden implementation of willTurnIntoFault is being called twice when undoing some code which originally created the object in question. This results in a crash when attempting to double-unregister for KVO on a key path.
The Apple documents say this is the right place to un-register for KVO.
A bit of context - the undo operation involves removing the corresponding view of the model from it's superview. The view retains it's model.
So my question is: what kind of programmer errors can result in willTurnIntoFault being called twice in a subclass of NSManagedObject?
Note: Previously I was overriding dealloc in this class but have since realised this is not recommended for subclasses of NSManagedObject. I've since moved this code into -didTurnIntoFault. I'm not currently overriding any other methods which the Apple docs say you should not override.
For posterity's sake: I had the same problem. In my case I had an object A with a (to-one) relation to an object B. When A got deleted B's inverse relation to A was set to null. This caused B's observeValueOfKeyPath:ofObject:change:context method to be invoked (where keypath was B's relation to A). Unfortunately this method checked a property of A, causing the faulting of A to be cancelled (note that in this situation awakeFromFetch does not get called--I presume because the object never actually did get to fault state). Hence I might might get a second call to willTurnIntoFault later and the object would try to unregister for KVO again, resulting in a crash--just like in the OP.
For me the solution was to change the delete rule for A to cascade, so that the B object got deleted when the A object got deleted AND to unregister for KVO in prepareForDeletion. This is important because the deletion of A will still cause B's inverse relation to be set to nil before B is actually deleted.
Note that prepareForDeletion gets called before but not instead of willTurnIntoFault. Hence, if you unregister for KVO in both, you need to maintain some state to make sure you have not already unregistered.
Seems the issue was caused by a custom setter method which was setting/unsetting KVO values from within willTurnIntoFault.
Related
For every managed object that is sent via iCloud update, this warning/error is sent to the console:
*** ERROR: this process has called an NSArray-taking method, such as
initWithArray:, and passed in an NSSet object. This is being
worked-around for now, but will soon cause you grief.
My managed objects are Clients and have a one to many relationship with assessments as shown below.
class Client: NSManagedObject {
//other NSManaged vars are here
#NSManaged var assessment: NSOrderedSet
}
Judging by the timing of this error (during ubiquitous updates) and the fact that this is my only use of NSSet objects in my project, I can presume that a function during this update is being passed an NSOrderedSet when its expecting an NSArray.
Turning off iCloud removes the errors.
I found two other folks with a very similar issue:
Using iCloud enabled Core Data NSArray-taking method
Core Data Relation between Objects
However, neither offers any solution to my problem. Everything is working fine right now, however "it will soon cause you grief."
If this isn't resolved here, I'll take this issue up with the apple dev support.
I figured it out how to fix this error after much research. (No help in from the Apple Dev forums).
This is error is caused by the Swift 1.2 upgrade. They encourage you to use their new Set<> class instead of NSSet. The error was a vague way of reinforcing that claim.
Ordering of my data was ultimately handled by my NSFetchedResultsController, so I didn't need the stored data to be ordered. So I set out on a task to change the data type for my one to many relationship from NSOrderedSet to Set.
So I created a new data model, selected my relationship and unchecked "Ordered" in the data model inspector. Then migrated to the new model in the next run. (as shown below)
Once that was done, I changed the data type in my managed object subclass from NSOrderedSet to Set. (or Set). That generated compiler errors that were easy to fix throughout my code.
The new Set class easily converts to an array: Array(mySet)
To insert an object to the set, there's an easy insert method. foo.mySet.insert(objectToInsert).
Side note: Converting my relationship to a Set also fixed some weird ordering issues I was having with my table views and the NSFetchedResultsController.
Then I ran the program, generated data, uninstalled the program. Ran the program again and watch the glorious iCloud data populate without the annoying errors.
Boom. I hope this saves someone out there the 10 hours of turmoil (I tried a lot of different things..) I spent to fix this.
I use the method “deleteObject:” to delete a NSManagedObject, but after this I still can print the NSManagedObject although it no longer associates with any other NSManagedObject.
How lead to this and why?
deleteObject: will delete the parameter NSManagedObject from the context. That doesn't mean it will make your object be nil. In fact, it has no way of doing so as you're passing a reference and not the address of it. Most likely it's already doing what you want.
It seems controllerDidChangeContent: is being called as soon as I create a new managed object in my context. The documentation seems to suggest this method is called only once you save: the context.
This "bug" if it is one, is causing my application to crash because as part of my table view cell, I need to load other managed objects that don't exist at the time of creating the main managed object.
Someone seems to have spotted this too, please check out the following link and I would love to hear your opinions on this: http://openradar.appspot.com/10207615
More information
Although the link I added to this post showcases an example using two NSManagedObjectContext, my application is using one context, but the controllerDidChangeContent: is being messaged none the less as soon as an object is created in the one and only context, and controllerDidChangeContent: is being called a second time when I save: this context. It is to my understanding that this method should only be messaged when the context is saved.
The solution is to avoid dealing with more than one managedObjectContext. If your cell needs to load other managed objects, it should still use the same managed object context as the main managed object.
I have yet to see a use case where it is absolutely unavoidable to use more than one managed object context referring to the same model active at the same time.
This question covncerns my lack of understanding of how to use the core data undo manager and how to restore a NSManagedObject to its state before editing was done.
I am just learning my way around Core Data. I have my NSManagedObject classes set up with their dynamic accessors. I perform a fetch that returns several NSManagedObject entity results. Content from each of these entity results (first name, last name) get put into a table view, and then the user picks one out of the table for detailed view and then editing.
The detail view controller receives a pointer to the selected NSManagedObject entity. As the user edits the fields, the corresponding property value in the NSManagedObject entity is updated. This seemed like the cleanest way to manage these changes.
Now, rather than committing the changes using save, I want to provide a cancel-editing feature that rolls back to what is in the data base for that entity. I really only want to restore the one entity and not perform the entire refetch.
I tried rollback and I tried NSUndoManager (with beginUndoGrouping and endUndoGrouping), and that is not working. I don't think I understand what rollback is really supposed to do.
But in any case, I still want to restore the property values in just that single entity (taking the lazy approach to only fetch what is needed, which is the one entity) so that my detail view controller can refill its view with the correct information. Right now it is using the NSManagedObject entity values, which contain the edited values, which were cancelled.
I suppose I could just start the edit process by creating a copy of the NSManagedObject. If the cancel-editing button is pressed, I could copy it back into the original. (I might even be able to just replace the original with the copy by moving the pointer. But since the pointer has actually been passed through several objects, I'm not sure how to manage the retain number on the copy.)
Does anyone have any other suggestions?
Thanks
Using rollback should accomplish what you want and I'm not sure what it doesn't. It is probably an implementation detail error.
You can find the specific managed object/s that were updated but not yet saved by calling the context's updatedObjects.
I have two entities event and time. The event entity has a 1 to many relationship to time entities as each event can be performed multiple times. Now I want to display all the events chronologically in a tableView. So I set up a fetchedResultsController to fetch all time objects, sort them according to the start time and display the event information by using the relationship to the event object. So far so good. But now if the user tabs an entry in the table I pass an event object to the detailViewController where the event can be edited.
The problem is that now only the event entity is marked as updated. I found this out by looking at the userInfo directory of the NSManagedObjectDidChange notification. In consequence the delegate methods on the FRC are not fired as no time objects have been changed.
How can I manually mark a time object as changed to make the FRC recognize the changes and update the cells accordingly? I tried firing the KVO methods willChangeValueForKey and didChangeValueForKey but it did not work so far.
Thanks alot
Thomas
My model is a little different, but it can easily be translated to your one.
I got a tree-like structure:
Element
title
parent (to-one)
Folder : Element
children (to-many)
File : Element
When a file gets added or deleted, only the first folder in the queue up gets notified about this change. When a file's title changes, not a single folder would get notified. So, what to do?
I tried overriding -willChangeValueForKey: and -didChangeValueForKey: in my Element class.
- (void)willChangeValueForKey:(NSString *)key
{
[super willChangeValueForKey:key];
[self.parent willChangeValueForKey:#"children"];
}
- (void)didChangeValueForKey:(NSString *)key
{
[super didChangeValueForKey:key];
[self.parent didChangeValueForKey:#"children"];
}
Basically, what this does is forcing the parent folder to update because one of its children changed.
Hope it works for you, too.
I'm working through some similar types of updates right now as well. Here's the way I approached the problem.
Let's say we have object A, which relates to object B. B has a property C. We want changes to property C to be reflected in FRCs that use A as the fetched object. What I did to make this happen was to define an accessor to property C from object A:
//A.m
- (void)setC:(int)cValue {
[self willChangeValueForKey:#"b"];
self.b.c = cValue
[self didChangeValueForKey:#"b"];
}
- (int)c {
return self.b.c;
}
This allowed my cells to update based on FRC callbacks with type NSFetchedResultsChangeUpdate. Hopefully this helps solve your problem.
The answer above from #Jenox appears to be the right idea, but it's best to not override those methods as they're called whenever any key is changed on the child object and will probably impact performance and cause unexpected side-effects (it did for me). Probably best to just call them in whatever method you make the changes to the child object in, like this:
- (void)updateFromDictionary:(NSDictionary *)aDictionary {
[myParentModel willChangeValueForKey:#"myChildObject"];
[super updateFromDictionary:aDictionary];
[myParentModel didChangeValueForKey:#"myChildObject"];
}
Note that updateFromDictionary is one of my methods, not a system method.