Grand Central Dispatch (GCD) + Key-Value Observing (KVO) - objective-c

I have a method in which I add observers:
- (void) method
{
[currentPlayer addObserver:self forKeyPath:#"some" options:some context:some];
}
All the changes are processed in these method:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
If I modify my method to:
- (void) method
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[currentPlayer addObserver:self forKeyPath:#"some" options:some context:some];
});
}
Does it mean that - (void) observeValueForKeyPath... will work in DISPATCH_QUEUE_PRIORITY_HIGH?
P.S. I want KVO to work in DISPATCH_QUEUE_PRIORITY_HIGH.
EDIT:
I observe AVPlayer variables. Such as:
[currentPlayer addObserver:self forKeyPath:#"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:kTimeRangesKVO];
These variables are changed automatically.
Every time "observeValueForKeyPath" is called I check the queue and it remains dispatch_get_main_queue() and I don't know how to change it to some other queue.

As an observer you can't control what thread / queue observeValueForKeyPath is called from. You could wrap a dispatch onto the queue you wish around the work this method doing. That will move the work of the observation to the high priority queue.
Just be sure you're aware this work is likely to be running on a different thread / queue than the code that triggered the observation. If the object you're observing is not thread safe you will need to inspect it prior dispatching the rest of the work onto a different queue.

No, if you want that the observeValueForKeyPath method gets executed with a higher priority, you have to change the key value from a thread with higher priority.KVO works synchronously, so the observeValueForKeyPath method gets executed on the same thread where you change the key value.

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.

Core Data dependent property on to-many relationship

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.