Removing observers for an NSOperation - iphone

I have a view which loads data via an NSOperation within an NSOperationQueue. I want to allow users to leave this view before the operation has completed. My problem is that I can't seem to consistently do this without crashing. Here is my code to start the operation:
NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release];
SportsLoadOperation* loadOperation = [[SportsLoadOperation alloc] init];
[loadOperation addObserver:self forKeyPath:#"isFinished" options:0 context:NULL];
[self.queue addOperation:loadOperation];
[loadOperation release];
If I leave the view while the operation is still executing, I often get this error:
[SportsViewController retain]: message sent to deallocated instance 0x38b5a0
If I try to remove the observers so that this doesn't occur, like this:
-(void)viewWillDisappear:(BOOL)animated {
if (self.isLoadingData) {
for (NSOperation *operation in [self.queue operations]) {
if([operation isExecuting]) {
[operation cancel];
[operation removeObserver:self forKeyPath:#"isFinished"];
}
}
}
[super viewWillDisappear:animated];
}
Then I sometimes get this error:
Terminating app due to uncaught exception 'NSRangeException', reason:
'Cannot remove an observer <SportsViewController 0x661c730> for the key path "isFinished" from <SportsLoadOperation 0x66201a0> because it is not registered as an observer.'
How can I avoid these problems?

The 2nd error message says it all.
Have you tried to not removeObserver after [operation cancel] and see what happens then?
Have you tried to first removeObserver and only then cancel the operation?
These might help to narrow down the conditions that trigger the error. Also, you might want to add log output to the code to see when it actually executes.
And, like freespace's answer says, adding & removing observers is best done in the construction / destruction methods of the observed instances. This generally yields more stable code.

Looks like you have an instance of SportsLoadOperation that doesn't have SportsViewController as an observer. Are you inserting SportsLoadOperation anywhere else in your code? If this is the case, consider writing an initWithObserver method for SportsLoadOperaion that will do the observing automatically. This avoids errors caused by forgetting to set the observer on isFinished.
Also, it is probably better to do the removal of observer in dealloc then in viewWillDisappear because viewWillDisappear is called in many circumstances, e.g. when displaying a modal view controller. Thus you might be prematurely stopping your operations.
Edit
Instead of checking against [operation isExecuting] check against [operation isCancelled]. This is because [operation cancel] doesn't stop an operation - it can and will continue executing if you don't actually check for isCancelled in your main method. This means that if viewWillDisappear is called twice, you could end up attempting to call removeObserver twice on the same instance of SportsLoadOperation, with the second attempt failing.
Add some debugging output statements in the following places:
when you create a SportsLoadOperation instance and insert it into the queueu
when you are cancelling a SportsLoadOperation instance and removing from it observers.

I ended up solving this by overriding the observed operation's addObserver and removeObserver methods, to keep track of observers so I could remove them in the [cancel] method.
All I have to do now is call the operation queue to cancel all operations before I dismiss the controller that was observing the operations.
Below, _observers is an NSMutableDictionary.
- (void)addObserver:(NSObject*)observer
forKeyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options context:(void*)context
{
[super addObserver:observer forKeyPath:keyPath options:options context:context];
[_observers setValue:observer forKey:keyPath];
}
- (void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath
{
[super removeObserver:observer forKeyPath:keyPath];
[_observers removeObjectForKey:keyPath];
}
- (void)cancel
{
[super cancel];
for(id key in _observers)
{
id object = [_observers valueForKey:key];
[super removeObserver:object forKeyPath:key];
}
[_observers removeAllObjects];
}

Related

UI hangs inspite calling a lengthy method in background thread

I am calling a method like methodA in background.now if i call a lengthy method called methodB from methodA.should i separately mention it to be in background.the reason i ask this question is inspite of calling the lengthy process in background thread,the ui hangs for some time.
ie
[self performSelectorInBackground:#selector(methodA)];
-(void)methodA
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
[self methodB];
[pool drain];
}
-(void)methodB
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
//some lengthy process
[self performSelectorOnMainThread:#selector(updateTable) withObject:nil waitUntilDone:NO];
[pool drain];
}
-(void)updateTable
{
[self.tableview reloadData];
}
is this way of calling background method right?
If a selector (method) X is called on a certain thread (whether it be background or main thread), any selectors that X calls (in the conventional fashion) are also on that same thread. So no, you don't need to call performSelectorInBackground: for each sub-call from methodA: as long as the entry-point selector is on the 'correct' thread, anything it then does is also on the 'correct' thread, including calls to other methods.
Note that the NSAutoreleasePool you set up in methodB looks unnecessary -- you don't really need it, since you're already inside the scope of the NSAutoreleasePool set up in methodA. (Assuming that methodB is only called from methodA as in the example!)
Incidently, have you put in NSLogs to absolutely verify that //some lengthy process is actually the thing taking all the time?

UIProgressView update fail with UINotification

I try to solve this problem for several days now I have to ask you...
I've got a View (and a ViewController) with a UITableview. There is a TableViewController for that table which is generated in the ViewController. The TableViewController calls a DataSyncManager sharedInstant object (which is obviously in a separate class) which starts to sync data with the server.
I do it this way (first the refresh method):
-(void) refresh{
[serverQueueProgressView setProgress:0.0];
[syncingLabel setAlpha:0.5];
[serverQueueProgressView setAlpha:1];
[self performSelector:#selector(reloadTableViewDataSource) withObject:nil afterDelay:1.0];
}
Then the method reloadTableViewDataSource (of TableViewController) is called:
- (void)reloadTableViewDataSource
{
[dataSyncManager getEntriesFromServer];
}
dataSyncManager is my sharedInstance.
In the getEntriesFromServer method of dataSyncManager I do the loop with different sync items and call everytime
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ServerQueueProgress"
object:progress];
with the proper progress as NSNumber (that part works well). The message is now sent and catched by my ViewController (it works, I checked with a breakpoint, it also gets the right progress-NSNumber and converts it to float):
- (void)serverQueueProgress:(NSNotification *)notification {
if(![NSThread isMainThread])
{
[self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
return;
}
[queueProgressView setProgress:[[notification object] floatValue]];
}
This is one solution which I found here on stackoverflow. But the if is always skipped because obviously I'm on main thread.
Unfortunately the UIProgressview doesn't get updated, it just hangs around, but I connected it well in Interface Builder (I checked that by setting the progress in another method of ViewController.
I also tried to catch the Notification with my TableViewController and put in some other solutions, but no chance, the UIProgressView doesn't get updated live. Only after the sync is done.
Here is the mentioned code in TableViewController which also gets executed without errors (I also stepped it to make sure every line gehts executed well):
This is the method called when received a the notification:
- (void)serverQueueProgress:(NSNotification *)notification {
[self performSelectorOnMainThread:#selector(updateProgress:) withObject:[notification object] waitUntilDone:NO];
[serverQueueProgressView setProgress:[[notification object] floatValue]];
}
Which also calls updateProgress: of the same class:
- (void)updateProgress:(NSNumber *)newProgressValue {
[serverQueueProgressView setProgress:[newProgressValue floatValue]];
}
No chance. I tried many ways and implemented some in parallel as you see, but the ProgressView won't get updated live. Only at the end of syncing. What am I doing wrong??
EDIT: Here is my getEntriesFromServer and some other stuff in DataSyncManager:
- (void)getEntriesFromServer
{
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SynchingStarted"
object:nil];
[self completeServerQueue];
...
}
and completeServerQueue is the function which sends messages to my ViewController with the proper progress float value (it's only a dummy for loop, which gets executed properly... I've checked it):
- (void)completeServerQueue {
NSNumber *progress = [[NSNumber alloc] init];
for (int i=0; i<15; i++) {
progress = [[NSNumber alloc] initWithFloat:(100/15*i) ];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ServerQueueProgress"
object:progress];
sleep(1);
}
}
also, when you're having trouble, break the problem down a bit. Instead of:
[serverQueueProgressView setProgress:[[notification object] floatValue]];
do this;
float prog = [notification object] floatValue];
[serverQueueProgressView setProgress:prog];
then debugging would give a clue that this isn't working.
my guess is the problem isn't the code you've shown here, but other code in getEntriesFromServer. Are you using NSURLConnection? Something like:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
then you will get callbacks asynchronously that you can use to update your progress view.

Timed selector never performed

I've added an observer for my method:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(closeViewAfterUpdating)
name:#"labelUpdatedShouldReturn"
object:nil];
Then my relevant methods:
-(void)closeViewAfterUpdating; {
NSLog(#"Part 1 called");
[self performSelector:#selector(closeViewAfterUpdating2) withObject:nil afterDelay:2.0];
}
-(void)closeViewAfterUpdating2; {
NSLog(#"Part 2 called");
[self dismissModalViewControllerAnimated:YES];
}
The only reason why I've split this method into two parts is so that I can have a delay before the method is fired.
The problem is, the second method is never called. My NSLog output shows Part 1 called, but it never fires part 2. Any ideas?
EDIT: I'm calling the notification from a background thread, does that make a difference by any chance?
Here's how I'm creating my background thread:
[NSThread detachNewThreadSelector:#selector(getWeather) toTarget:self withObject:nil];
and in getWeather I have:
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateZipLabel" object:textfield.text];
Also, calling:
[self performSelector:#selector(closeViewAfterUpdating2) withObject:nil];
does work.
EDITx2: I fixed it. Just needed to post the notification in my main thread and it worked just fine.
The background thread is the problem. It has a non running run loop, thus the selector is never called. Just let the NSRunLoop or CFRunLoopRef object of the thread run while the selector isn't fired.
I tried your code and it works fine on my side. You might be doing something funky in the background that is interrupting your selector.
You have a semi-colon in the method definition:
-(void)closeViewAfterUpdating2; {
Is this present in the code or a copy/paste issue? That would be the problem on why you never see it called.

Delegate functions not being called

Long time lurker, first time poster.
I'm making a ServerConnection module to make it a whole lot modular and easier but am having trouble getting the delegate called. I've seen a few more questions like this but none of the answers fixed my problem.
ServerConnection is set up as a protocol. So a ServerConnection object is created in Login.m which makes the call to the server and then add delegate methods in Login to handle if there's an error or if it's done, these are called by ServerConnection like below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( [self.delegate respondsToSelector:#selector(connectionDidFinish:)]) {
NSLog(#"DOES RESPOND");
[self.delegate connectionDidFinish:self];
} else {
NSLog(#"DOES NOT RESPOND");
}
self.connection = nil;
self.receivedData = nil;
}
It always "does not respond". I've tried the CFRunLoop trick (below) but it still doesn't work.
- (IBAction)processLogin:(id)sender {
// Hide the keyboard
[sender resignFirstResponder];
// Start new thread
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Acutally call the server
[self authenticate];
// Prevent the thread from exploding before we've got the data
CFRunLoopRun();
// End thread
[pool release];
}
I copied the Apple URLCache demo pretty heavily and have compared them both many times but can't find any discrepancies.
Any help would be greatly appreciated.
Here are the questions to ask:
Does your delegate respond to connectionDidFinishLoading:?
Does the signature match, i.e. it takes another object?
Is the delegate set at all or is it nil? (Check this in that very method)
If any of those are "NO", you will see "doesn't respond"... and all equally likely to happen in your application, but all are easy to figure out.

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.