Core Data dependent property on to-many relationship - objective-c

I'm trying to update a custom property in an NSManagedObject whenever an item that it has a to-many relation to changes. I've overridden the following methods as prescribed by apple:
- (void)addDevicesObject:(Device *)value;
- (void)removeDevicesObject:(Device *)value;
- (void)addDevices:(NSSet *)value;
- (void)removeDevices:(NSSet *)value;
and inside the implementation I add or remove observers on the changed objects. The problem is my override methods are not called when my bindings based UI makes changes to the data. How should I go about doing this?

If the custom property is calculated when it is asked for, use +keyPathsForValuesAffectingValueForKey: to trigger update notifications when devices is changed.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if([key isEqualToString:#"MyCustomProperty"]) return [NSSet setWithObject:#"devices"];
return [super keyPathsForValuesAffectingValueForKey:key];
}
If you want to perform calculations when devices is changed only, then use KVO to get notified when it is changed.
//Put this in the various awake... methods
[self addObserver:self forKeyPath:#"devices" options:0 context:nil];
//Put this in the didTurnIntoFault method
[self removeObserver:self forKeyPath:#"devices"];
- (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(object == self && [path isEqualToString:#"devices"]) {
//Update custom property here
} else [super observeValueForKeyPath:path ofObject:object change:change context:context];
}

This is the same problem I solved in a little github project
Using observings to solve the problem is a possible way, still you need to consider many core data specific problems with faulting (observing a property of a faulting object) and undo/redo/delete. If you want to stay 10.5 compatible the important method awakeFromSnapshotEvent is also missing and you need a workaround to activate observings for undoing delete objects after a context save.
The setter:
- (void)setDevices:(NSSet *)newDevices
should also be called in bindings if you want to avoid observings which can be complicated.
Setters are NOT called in undo/redo operations! So you should store your dependent value in a core data modeled property.

Related

How to set value without trigger KVO

I use the following code to add KVO on object.
[self.model addObserver:self
forKeyPath:#"userName"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
Now I want to set the userName like below. Yes, it will trigger KVO.
self.model.userName = #"testUser";
However, I want to set value without trigger KVO. How to do that? Are there any method like below allowing me to do that?
[self.model setValue:#"testUser" forKey:#"userName" isSilent:YES];
Your design is broken if you want to do this. The point of key-value observing is that someone wants to know when a field changes so they register for notifications. The point of being key-value observing compliant is that you're keeping your options open as to how the rest of the system interacts with you.
What it sounds like you're doing is trying to hack around some problem where you don't want someone to know the true value of a property for some reason. So they think they're getting updates but actually if they were to check the property then it'd turn out you were deliberately lying.
As you'd expect, Cocoa doesn't have any mechanisms to support such hacks. They're extremely bad practice, breaking the whole structure of object-oriented programming.
Lecturing aside, you could write a custom setter that went directly to the instance variable. So, e.g.
- (void)setUserNameAndLieAboutItAsAShortSightedHack:(NSString *)newName
{
_userName = newName;
}
At the system level, key-value observing is implemented by creating a new version of the property setter that contains a call to the real setter and makes appropriate observer calls around the outside. So avoiding the real setter would avoid the notifications.
Core Data implements setPrimitiveValue:forKey: to allow you to do this. You can implement the same method in your object.
[self.model setPrimitiveValue:#"testUser" forKey:#"userName"];
When doing this however, it should be in the context of aggregating notifications where the observer is eventually notified with manual willChangeValueForKey: and didChangeValueForKey:.
You can use an ignore flag. Same idea as in the docs for User-Driven Updates.
// update the config object with the value the user chose.
- (IBAction)valueChanged:(UISlider *)slider{
self.ignore = YES;
self.config.sliderValue = slider.value;
self.ignore = NO;
}
// update the slider with the value from the config object.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kSliderKVOContext) {
if(!self.ignore){
self.slider.value = [change[NSKeyValueChangeNewKey] doubleValue];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

How can I invoke a method as soon as a bool flag changes?

I want to perform [document doSomething] as soon as [document isBusy] is false. What is the best way to do it? I tried it with a while-loop but that delays the following code, which is not what I want.
If the action a semantic of your document model
From your question both isBusy and doSomething are features of your document and the action to doSomething when isBusy goes false is appears to be a semantic of your document model and should therefore be implemented by your document model, i.e. something like:
- (void) setIsBusy:(BOOL)flag
{
if(flag != _isBusy) // check if this is a change
{
_isBusy = flag;
if(flag)
{
// doSomething, or schedule doSomething if it is a long operation etc., e.g.
[self doSomething];
}
}
}
Using KVO to implement semantics within a single object is probably unusual, but there are cases where it is useful. In this case it would replace a direct action with an indirect one - KVO would execute doSomething at exactly the same point as the above sample code, there would just be a number of intermediate system methods between setIsBusy and doSomething plus the associated overhead of setting up the KVO.
If the action is a semantic of your document's client
Of course, if the linkage between these two is independent of your document model, i.e. is a semantic of the client of your document, then KVO is appropriate and would be implemented in your client. Your client would register as an observer of your document, i.e. something like:
[document addObserver:self
forKeyPath:#"isBusy"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
in a method of the client. Then in your client when a notification of the change is received take the appropriate action:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"isBusy"] && object == document)
[document doSomething;
else
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
If isBusy has a associated setter method, you can call doSomething in that setter. A better option would be to use Key Value Observing to observe the value of isBusy and take action when it changes.
Use KVC/KVO (Key Value Coding and Key Value Observation). The subject is covered well in the Apple Documentation. KVO and KVC is a basic programming technique which provides may solutions to your problem.

How do I observe the "operations" property of NSOperationQueue?

I want to be notified whenever a NSOperation has been added or removed from my NSOperationQueue. I'm trying to set up key-value observing for the "operations" property (an array of NSOperations currently in the Queue) but its not being fired. Is there something wrong with my syntax?
#implementation myOperationQueueSubclass
-(id)init
{
if (self = [super init])
{
// Initialization code here
[self addObserver:self
forKeyPath:#"operations"
options:0
context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"queue changed...");
if ([keyPath isEqualToString:#"operations"]) {
if (self.operationCount == 0) {
// No ops
} else {
// Has ops
}
}
}
There's nothing wrong with your syntax, but you're observing the array property, not the array itself (which doesn't issue KVO notifications when it's mutated anyways).
You'll get notified if it's reassigned for some reason, but NSOperationQueue would have to take the trouble to make the notifications manually, or use the KVC accessors, to allow others to see when objects are added to or removed from its operations.
I had a similar need and created a very thin operations manager, OperationsRunner in this github project. Other objects interface with this class instead of the NSOperationsQueue directly. It has only a handful of methods - run an operation, cancel it, ask for the number of operations in the queue, etc.
What I did was to use a mutable set to hold a reference to an operation that was added to the operations queue, and remove it when the operation completed or cancelled - sort of a shadow container.
The nice this about this class is that you can easily add it to any kind of other class to manage operations, and quickly cancel all pending operations.

How to programmatically monitor KVC object?

I'm trying to monitor a NSMutableArray for changes via code. I want to add an observer for whenever the array changes, but I don't see what the NotificationName is supposed to be to make that happen.
Basically, when the array is modified, I want to execute a custom selector.
I'm not 100%, but I'm pretty sure that Key-Value Observing is what you want.
Whatever object it is that cares about the array registers itself as an observer:
[objectWithArray addObserver:self
forKeyPath:#"theArray"
options:NSKeyValueObservingOptionNew
context:nil];
It will then receive notice that the array has changed:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"Change is good: %#", [change objectForKey:NSKeyValueChangeNewKey]);
}
Note that this one method will collect all the observations that this object has registered for. If you register the same object to observe many different keys, you will likely have to differentiate them when this method gets called; that's the purpose of the keyPath and object arguments.
The problem, and the reason I'm not sure if this will work for you, is that this assumes that the array is in your code, because you need to wrap accesses to it in order for the notification to be sent.
[self willChangeValueForKey:#"theArray"];
[theArray addObject:...];
[self didChangeValueForKey:#"theArray"];
An arbitrary framework class will have some properties which are, and some properties which are not, Key-Value Observing compliant. For example, NSWindow's firstResponder is KVO compliant, but its childWindows is not. The docs, of course, will tell you which are which.

Why are my Cocoa bindings broken?

I have a window with an NSTextField (in Snow Leopard), which I have binded to an NSString function in my WindowController class. This string will combine information about my table view's selection and count, provided by my array controller. It gets an initial value, "0 0", but doesn't ever update, when the selection or count changes. The binding looks like this (File's Owner is MyWindowController):
I implemented + (NSSet *)keyPathsForValuesAffecting<key> (below), but the binding never updates, even when the array controller's total count and selection change.
(Additional troubleshooting performed) I had originally been using the Display Pattern Value binding of the NSTextField, but I needed more complicated logic than that binding afforded. I then started listening to the selection changed/changing events of the TableView that displays the array controller's contents and changing the Display Pattern Value bindings dynamically, but that felt like a hack, and overly complicated.
I'm sure there's something I'm missing, but I can't tell what. Does anyone have any ideas? I've read through Apple's key-value-observing documentation, and this seems to be all that's necessary. I've checked, and my keyPathsForValuesAffectingMyString is getting called, but myString only gets called once. I've distilled my code below (updated x3).
Update 1/21
I'm still plugging away trying to figure this out. When I addObserver to self for the arrayController key paths, the notifications do fire as expected, so my key paths and the key value observing mechanism is fine. When I call [self didChangeValueForKey:#"myString"]; within my observeValueForKeyPath method for the same keys, the binding still doesn't update, leading me to believe it's a bindings problem rather than a KVO problem. I'm going to be reading up on the bindings mechanism more...
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
}
- (NSArrayController *)arrayController;
- (NSString *)myString;
#end
#implementation MyWindowController
+ (NSSet *)keyPathsForValuesAffectingMyString {
return [NSSet setWithObjects:
#"arrayController.arrangedObjects",
#"arrayController.selection",
nil];
}
- (NSArrayController *)arrayController {
return arrayController;
}
- (NSString *)myString {
// Just as an example; I have more complicated logic going on in my real code
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end
I’ve verified this exact same bug. Someone on Cocoabuilder had a guess as to why the bug happens:
http://www.cocoabuilder.com/archive/cocoa/284396-why-doesn-nsarraycontroller-selection-et-al-fire-keypathsforvaluesaffectingkey.html#284400
I can’t speak as to whether this explanation is true, but I certainly can’t get +keyPathsForValues… to work with NSArrayControllers.
I've got a workaround, but I'm not happy about it, since it shouldn't be necessary, and I would still prefer to get the bindings working properly. I won't accept this answer, and will delete it if someone posts an actual fix. </disclaimer>
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
IBOutlet NSTextField *fieldThatShouldBeBinded;
}
- (NSString *)myString;
#end
#implementation MyWindowController
- (void)awakeFromNib {
[arrayController addObserver:self
forKeyPath:#"selection"
options:0
context:NULL];
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:0
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if( object == arrayController )
[fieldThatShouldBeBinded setStringValue:[self myString]];
}
- (NSString *)myString {
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end
Make sure that the arrayController outlet is connected in Interface Builder. I'm guessing that it's nil.
Don't use the #count keyword. Bindings and KVO on array controllers will get updated when the content changes. If that doesn't work, then there is a problem somewhere else.
Another option is to use the display pattern bindings instead of a composite property. Bind Display Pattern Value1 to arrayController.arrangedObjects.#count and Display Pattern Value2 to arrayController.selection.#count, and set the pattern to "%{value1}#, %{value2}#"
I met the same problem and found another way (but it is still workaround).
You have to declare dynamic workaround property. In implementation section, just return new empty object for it. Now, you can KVO this workaround property.
#property(nonatomic,retain) NSArray *workaround;
#dynamic workaround;
- (NSArray *)workaround { return [NSArray array]; } // new *every* time
- (void)setWorkaround:(NSArray *)unused { }
+ (NSSet *)keyPathsForValuesAffectingMyString { return [NSSet setWithObject:#"workaround"]; }
To get this work, you still need to manually bind self.workaround to arrayController.selectedObjects (or whatever):
- (void)awakeFromNib // or similar place
{
[super awakeFromNib];
[self bind:#"workaround" toObject:arrayController withKeyPath:#"selectedObjects" options:nil];
}
Manual binding works as expected, workaround is updated with what you have bound it to. But KVO tests whether property value is really changed (and stops propagating if it is the same). If you return new self.workaround value every time, it works.
Warning: never call -[setWorkaround:] by yourself — this will effectively flush the other side of binding (arrayController.selectedObjects in this case).
This method has some benefits: you avoid centralized observeValueForKeyPath:... and your logic is in the right place. And it scales well, just add workaround2, 3, and so on for similar cases.