NSOperation KVO problem - iphone

I'm using following function to get my application notified after the operation in nsoperationqueue has finished, so that I can schedule the task that's dependent upon the result of the operation. I'm using:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if([keyPath isEqual:#"isFinished"] && _operation == object)
{
NSLog(#"Our Thread Finished!");
[_operation removeObserver:self forKeyPath:#"isFinished"];
[self performSelectorOnMainThread:#selector(showDialog) withObject:nil waitUntilDone:YES];
}
}
My question is since mostly the tasks assigned to these operations are parsing of data if I try to tap some other button or basically do something that results in action, I get the following exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<Settings: 0x21b970>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: isFinished
I perfectly understand that since I try doing other things on main thread, because of which the call to main thread:
[self performSelectorOnMainThread:#selector(showDialog) withObject:nil waitUntilDone:YES];
fails to get executed. But what's the solution to this problem as I want both allow user do any action after making a request and also perform the action scheduled after finishing the task assigned to the operation.
Is it really possible?
Thanx in advance.

If you can require Mac OS X 10.6 Snow Leopard, or (I think) iPhone OS 3.0, you can avoid KVO entirely for this. Just create another operation for the work you want to do on the main thread, add the operation it needs to follow as a dependency, and then put the main-thread operation on the main queue:
NSBlockOperration *mainThreadOp = [NSBlockOperation blockOperationWithBlock:^{
[self showDialog];
}];
[mainThreadOp addDependency:backgroundOp];
[[NSOperationQueue mainQueue] addOperation:mainThreadOp];
NSOperation supports dependencies between operations on different queues. Just be careful not to make operations on different queues mutually dependent, because that will result in deadlock.

The reason your are seeing this problem is that you are not using the context pointer.
When you add or remove an observer, pass a context pointer. In your observeValueForKeyPath:ofObject:change:context: , check the context point to make sure the observation being passed belongs to you. If not, call super. It's possible to use self as the context point, or you can take the address of a static string, etc.
Here is an example:
Adding the observer:
[sample addObserver:self forKeyPath:#"finished" options:[self observationOptions] context:(void *)self];
Handling the change notification:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (__bridge void *)self){
if ([keyPath isEqualToString:#"finished"]){
// Handle the change here.
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Removing the observer:
[sample removeObserver:self forKeyPath:#"finished" context:(void *)self];
Because your code was not able to pass one of the notifications to super in observeValueForKeyPath:ofObject:change:context:, that notification went in but never came out. This is why you got that exception.
It unfortunate that many of the Key-Value Observing examples available on The IntarWebs do not do this correctly and/or pass NULL as the context pointer (even Apple's documentation does this).

Related

Asynchronous download an object property

I'm programming a feed reader on the iOS platform and, when parsing the json result, i create an array with objects that represent every feed. These object'll be successively push to a NSTableView or to a map (they havgeolocated information). Sometimes happen that the feed does not return the location information, but return the foursquare Id of the place, so I have to contact the foursquare server and get the information.
Wath's the best way to do this? (I so want to know when all the feed in the array has retrieved their information, so I can push all on a map and in the tableview)
Thanks
You could create an NSOperationQueue and add the operations to fetch the information from the server, then observe the "operations" key path of the queue. When operations.count == 0, then do your push to the map and tableview.
Edit:
You aren't polling, per se - you are observing the property and receiving notification of any changes.
Add yourself as an observer of your queue's operations property.
[self.runningQueue addObserver:self forKeyPath:#"operations" options:0 context:NULL];
Then implement the following:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (object == self.runningQueue && [keyPath isEqualToString:#"operations"]) {
if ([self.runningQueue.operations count] == 0) {
// push to table view and map view
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
See this thread.

Observing insertions and removals from NSMutableSet using observeValueForKeyPath

I would like to be notified about a new insertion in the NSMutableSet and thus this is what I am doing, but for some reason it is not calling observeValueForKeyPath method
Just for test:
-(void)observ{
[self addObserver:self forKeyPath:#"connections" options:NSKeyValueChangeInsertion context:NULL];
[connections addObject:#"connectionName"];
}
This is never called:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if( [keyPath isEqualToString:#"connections"] ) {
NSLog(#"added new object");
}
}
Is NSMutablSet KVC ?
NSMutableSet is indeed KVO/KVC compliant. However, in order to receive the notifications with the way you have this set up, you need to implement the KVC accessor methods for a set. Information can be found here. Essentially, you have to implement methods called:
-countOfConnections
-enumeratorOfConnections
-memberOfConnections:
-addConnectionsObject:
-removeConnectionsObject:
-intersectConnections:
You must use these methods to access and mutate your set in order to receive KVO notifications.
Finally, in your -observeValueForKeyPath method, you can use the value of the key kind in the change dictionary to determine what type of mutation occurred (addition, deletion, etc.). The values can be found here and are listed under "NSKeyValueChange". Hope this helps.

Core Data - Watch for changes and register local notifications

I'm relatively new to Core Data and KVC, but I would like some pointers on registering listeners for changes in Core Data objects. Here's the situation:
I have one NSManagedObject called Patient and another called Medication. A Patient may have many Medications, and a Medication has a startOn and endOn dates.
I'd like to somehow listen for changes to the endOn property of all Medication objects. When a change occurs, I would like to schedule a local notification on the iOS device. I've worked with local notifications before, but don't know where to put the code for it in this context.
Do I create the scheduling code in the App Delegate and somehow register the App Delegate to listen for changes in Medication objects? Does this need to be attached to the NSManagedObjectContext?
How is this done? Pointers would be much appreciated!
Thanks!
With Key Value Observing, you need some instance to do the observing. Sometimes that can be the same object that calls -setEndOn: on Medication; sometimes it might have to be something else. Let's assume that your app has a MedicationManager class - of which one instance is created. And, further assume that MedicationManager has an instance method -createMedicationWithName:startOn:endOn: like this:
- (Medication*) createMedicationWithName:(NSString*)medName startOn:(NSDate*)startDate endOn:(NSDate*)endDate
{
// Create and configure a new instance of the Compound entity
Medication *newMedication = (Medication *)[NSEntityDescription insertNewObjectForEntityForName:#"Medication"
inManagedObjectContext:[self managedObjectContext]];
[newMedication setName:medName];
[newMedication setStartOn:startDate];
[newMedication setEndOn:endDate];
// Set up KVO
[newMedication addObserver:self
forKeyPath:#"endOn"
options:NSKeyValueObservingOptionNew
context:nil];
return newCompound;
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:#"endOn"])
{
// ... schedule local notification on the iOS device for (Medication*)object.
return;
}
}
Or something like that.
Note, when you delete a Medication, then you would want to removeObserver... Also, when launching your app, you would need to establish MedicationManager as observer for existing Medications. I think that this could be as simple as iterating over all Medications and calling addObserver for each. If you have many Medications, then you might want to do this in a more 'lazy' manner (i.e., in -awakeFromFetch).
You will have to register observers when objects are fetched from the store the first time as well as when they are created. Instead of having to loop through all of the entries when you first fetch (which is error prone especially on the iPhone when unmodified fetched objects can be faulted when not retained) just use the awakeFromFetch and awakeFromInsert messages.
Also, in the example code below you can also keep up with the Patient's aggregate information like the soonest startOn and the soonest endOn by making transient properties on the Patient which store this information. The following code observes changes to the endOn in the Medication and offers you the ability to update the Patient's aforementioned transient "soonest endOn" or "soonest startOn"
- (void)addMyObservers
{
registeredObservers_ = YES;
[self addObserver:self forKeyPath:#"endOn" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)awakeFromInsert
{
// called when you create this object
[super awakeFromInsert];
[self addMyObservers];
}
- (void)awakeFromFetch
{
// called when you fetch this old object from the store
[super awakeFromFetch];
[self addMyObservers];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"endOn"])
{
id newValue = [change objectForKey:NSKeyValueChangeNewKey];
// someone changed endOn so do something with this "newValue"
// check to see if the Patient needs the transient property for the soonest medication updated
// update any local notification schedule
}
}
// this is only required if you want to update the Patient's transient property for the soonest endOn or
- (void)setPatient:(Patient *)patient
{
[self willChangeValueForKey:#"patient"];
[self setPrimitivePatient:patient];
[self didChangeValueForKey:#"patient"];
// check to see if the Patient needs the transient property for the soonest medication updated
}

Queueing and Handling multiple requests on iphone

In my application, I'm using NSOperationQueue and NSInvocationOperation objects to execute all the operations. I have multiple queues in my application and I am using KVO "isFinished" on call of which I am performing the next operation on main thread.
The problem is: whenever I perform two operations one after another my application crashes saying:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<myViewController: 0x481b200>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: isFinished
Observed object: <NSInvocationOperation: 0x45d9ea0>
Change: {
kind = 1;
}
Context: 0x0'
My general code is as follows:
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(somemethod) object:nil];
[operation addObserver:self forKeyPath:#"isFinished" options:0 context:nil];
[operationQueue addOperation:operation];
[operation release];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if([keyPath isEqual:#"isFinished"] && operation == object)
{
[operation removeObserver:self forKeyPath:#"isFinished"];
[self performSelectorOnMainThread:#selector(newerPostLoadingNumberOfUdates) withObject:nil waitUntilDone:YES];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
I want to know wheather what is the best practice to enqueue operations that are requested and execute them accordingly?
Thanx in advance.
This will be difficult to answer w/o seeing more of your code as you mention using multiple queues. I see a few things to consider:
1) you have [operation release] and then you compare operation to object in your KVO handler. you should not release `operation until you are done with it. In the KVO handler in this case.
2) as mentioned above, you should do string compares using isEqualToString
3) it sounds like you are trying to run your queue sequentially such that only one operation is executed at a time and you queue another operation after the first has finished. Instead you could set your queue to only perform one operation at a time with:
operationQueue.maxConcurrentOperationCount = 1;
and then add all the operations at once. If order of execution matters, you can use Grand Central Dispatch directly and use dispatch_sync to execute blocks in a specific sequence on a queue.

NSOperation and NSOperationQueue callback

I have a class. Inside this class I pass an NSOperation into the NSOperationQueue that lives in my global variables.
Now my NSOperation is finished. I just want to know that it is finished in my class and to have the operation pass data to that class. How is this typically done?
I use the delegate pattern - that was the approach recommended to me by the guys at an Apple Developer Conference.
The scaffolding:
Set up a protocol MyOperationDelegate with a setResult:(MyResultObject *) result method.
Have whoever needs the result implement that protocol.
Add #property id<MyOperationDelegate> delegate; to the NSOperation subclass you've created.
The work:
When you create your operation, but before you queue it, tell it who should receive the result. Often, this is the object that creates the operation: [myOperation setDelegate: self];
At the end of your operation's main function, call [delegate setResult: myResultObject]; to pass the result on.
Another alternative... if your need to do some work when the operation is complete, you could wrap that work up in a block and use a dependancy. This is very simple, especially with NSBlockOperation.
NSOperationQueue* myQueue = [[NSOperationQueue alloc] init];
NSBlockOperation* myOp = [NSBlockOperation blockOperationWithBlock:^{
// Do work
}];
NSBlockOperation* myOp2 = [NSBlockOperation blockOperationWithBlock:^{
// Do work
}];
// My Op2 will only start when myOp is complete
[myOp2 addDependency:myOp];
[myQueue addOperation:myOp];
[myQueue addOperation:myOp2];
Also you can use setCompletionBlock
[myOp setCompletionBlock:^{
NSLog(#"MyOp completed");
}];
Add an observer to you class that listens for changes to the isFinished value of the NSOperation subclass
[operation addObserver:self
forKeyPath:#"isFinished"
options:NSKeyValueObservingOptionNew
context:SOME_CONTEXT];
Then implement the following method, having it look for the context your registered as your listener. You can make the data you want to retrieve from the NSOperation subclass available via an accessor method/property.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
Check out KVO Programming and Concurrency Programming.
Also, note that the observer will be received on the same thread as the Operation, so you may need to run code on the main thread if you want to deal with the UI.