Core Data: "-add<RelationshipKey>Object:" not called - macos

Let's presume I have an entity BikeRider with a relationship property called helmets.
I have an array controller bound to the app's managed object context, with entity set to BikeRider. There's a tableview that lists all bike riders.
Then, I have a second array controller, bound to the app's managed object context, with entity set to Helmet. Additionally, it's bound to bikeRiderArrayController.selection. There's a second tableview that lists all helmets for the selected bike rider.
I also have two buttons for adding and removing helmets. The setup works apparently flawlessly.
Except, of course for one small thing: it looks like -addHelmetsObject:, -removeHelmetsObject:, -addHelmets: and -removeHelmets: never get called. This means some code for setting up observation of each helmet's color property never gets called.
What am I missing? Isn't overriding addHelmets: et al (with proper willChangeValueForKey: et al notifications) the right way to get notified of additions?
Do I really have to [self observeValueForKey:#"helmets". . .] and then [oldValue minusSet:newValue] and vice versa to figure out which objects were added or removed? I could swear the methods were being correctly called in the past. Maybe some key element of the setup is now different.

This has never worked properly through NSArrayController. From Apple's docs:
Custom relationship set mutator methods are not invoked by an arraycontroller
Problem: You have implemented set mutator methods for a relationship as described in “Custom To-Many Relationship Accessor
Methods,” and have bound the contentSet binding of an
NSArrayController instance to a relationship, but the set mutator
methods are not invoked when you add objects to and remove objects
from the array controller.
Cause: This is a bug.
Remedy: You can work around this by adding self to the contentSet binding's key path. For example, instead of binding to [Department
Object Controller].selection.employees, you would bind to [Department
Object Controller].selection.self.employees.

Related

Extra Incorrect KVO Insertion Called on NSOrderedSet When Saving Child NSManagedObjectContext

I have an NSManagedObject with an NSOrderedSet to-many relationship named subpointConnections_. I am observing changes to this set using the following:
addObserver(self, forKeyPath: #keyPath(subpointConnections_), options: [], context: &subpointsOC)
This can be called only once in the initialisation of a lazy property on the NSManagedObject.
Any new relations are added in a child NSManagedObjectContext, and then saved down to my main context.
When I set the relationship, I use the following code, only setting the 'many' side of the relationship from the 'one':
superpoint.insertIntoSubpointConnections_(self, at: index)
insertIntoSubpointConnections_ is an autogenerated accessor.
In the child NSManagedObjectContext I see a single correct call to observeValue(forKeyPath..., that gives me the correct insertion index on subpointConnections_, when the above insertion code is called.
However, when I save to the parent NSManagedObjectContext I am seeing two calls to observeValue(forKeyPath... on the same object. The first call is for an insertion at the end of the NSOrderedSet. The second call is for an insertion at the correct position.
When evaluating the NSOrderedSet in each observation call, the new object is indeed at the position described - it is on the end of the set on the first call, and then moved to the correct position on the second call. However, I get no call to say that it was removed from the end position.
I am not sure if I have set something up incorrectly here. The many objects in my one-many relationship will be unique, so I could write the code to work around this, but that feels wrong.
Any thoughts on what I might be doing wrong?
Weirdly, the first change dictionary passed to observeValue(forKeyPath... contains an NSIndexSet, the second contains an NSMutableIndexSet.
EDIT
I have replicated this issue in a simple project here: https://github.com/GilesHammond/KVO-Core-Data-Extra/
Run in debug and select "ADD CHILD" multiple times from the app Main Menu. Observe the debug output showing the extra erroneous observation on the main NSManagedObjectContext.
I contacted Apple Support and have submitted this issue as a bug as suggested, as the given notifications are not atomic.
I had to take a different approach. I now update my NSManageObject when the NSManagedObjectContextDidSave notification is posted.

Two NSArrayControllers on one NSManagedObjectContext?

I have two NSArrayControllers in my MainWindow.xib and want to use both of them with CoreData. Both are using entities of the same type from the same data model. These two array controllers are bound each to their own table views (one for generated data, one for stored data).
The first array controller is connected to the managed object context in AppDelegate but what do I do about the second array controller? It doesn't seem it can be connected to the same managed object context. If I compile now I get the error Cannot perform operation without a managed object context for the second array controller.
To answer this question on my own now:
The binding inspector in IB has bindings on NSArrayController for the Managed Object Context. I wasn't completely sure that this is the right way to bind the array controllers but it obviously seems to be so.

Why would you bind the value of a NSProgressIndicator?

What's the point of binding the value of a NSProgressIndicator to your controller? It never seems to ask the controller for the value, except on startup. The only way to move the NSProgressIndicator seems to be by sending it #increaseBy:, which bypasses my binding. So, why would I bind?!
If your UI's bound value not updating, that means you either bungled the binding or your controller code is not modifying the bound value in a key-value-observing–compliant way. The most common problem is doing fooIvar = val rather than [self setFooIvar:val] or self.fooIvar = val.
Apple's answer to your problem:
[What to do if] Changing the value of a model property programmatically is not reflected in the user interface
If changes made to a model value programmatically are not being reflected in the user interface, this typically indicates that the model object is not key-value-observing compliant for the property, or that you are modifying the value in a manner that is bypassing key-value observing. You should ensure that:
The model class has automatic key-value observing enabled or implements manual key-value observing for the property.
That you are changing the value using an accessor method, or using a key-value-coding compliant method. Changing the value of an instance variable directly does not provide key-value observing change notifications.
If your model property is a collection, that you're modifying the content in a key-value-observing compliant manner. See “My collection controller isn’t displaying the current data” for more information.
For that answer and answers other common problems, see "Troubleshooting Cocoa Bindings."
You should also look at the examples provided by mmalc. They are a valuable resource.

Two NSArrayControllers and a relationship (CoreData)

Is it okay to do it like i did … ?
Created nsarraycontroller in entity mode controlling 'Accounts'
Created nsarraycontroller in entity mode controlling 'Transactions' and bound to the 'Accounts' nsarraycontroller with key 'selection' and model key path 'transactions' (to-many relationship from accounts to transactions)
I'm askin cause i think something is kinda messed up. Because when i create a button, and try to connect it with the add:(id)sender of the transactionsArrayController, it doesn't seem to add it ... Where can be the problem ? addObject works as expected. But I want to use add:(id)sender and also be sure the code is working as it has to ...
Yes, it's quite normal to base the contents of one array controller on some property of another (including selection.someCollection).
Do you receive any errors in the console when you add? (Hint: include them in your question) Are both array controllers' Managed Object Contexts bound to a valid MOC? What are all your other settings for both array controllers set to? (Another hint: you need to be a lot more specific about describing your setup.)

Cocoa bindings: custom setter methods?

I'm using Cocoa bindings to manage a table of objects. I understand how bindings work but I've run into a slight problem. Managing the table of objects would be fine and dandy, except that those objects have to manage actual bluetooth hardware. I'm working off of a framework that provides a class representing a connection to this hardware, and have made another "manager" class the makes it key-value compliant. In other words, this manager class has to be able to connect and modify its "connect" status in its properties dictionary, be the delegate of this hardware and modify properties, and update the hardware with changes made.
However, whenever I set new values within the object itself, like in a "connect" method that would change the "connect" key's value to 2 (looking), (i.e. propertiesDict = newDict), the change is not seeming to be picked up by observers that it is bound to. I've looked at the observeValueForKeyPath:ofObject:change:context: in the NSKeyValueObservingProtocol. However, I don't know what to do with the context argument.
I hope that makes sense... but if anyone has any ideas I'd love to hear them.
Your question isn't totally clear, but if I'm understanding it correctly the issue might be because you need to send manual KVO notifications before and after you change a value in the embedded object. For instance, [self willChangeValueForKey:#"connected"]; and [self didChangeValueForKey:#"connected"];.
There are three ways to update a property/attribute in a KVO compatible way:
Using the property setter (specified in #property declaration or generated by #synthesize)
Calling -willChangeValueForKey: and -didChangeValueForKey: before and after you change the property value in any way.
Calling -setValueForKey:

Resources