Queueing and Handling multiple requests on iphone - 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.

Related

Can I stop or cancel loop function when using [NSThread sleepForTimeInterval:2.0]; in IOS

I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.
I use this code to call loop function:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
delete=NO;
temp = [FileCompletedArray mutableCopy];
NSOperationQueue *queue = [NSOperationQueue new];
operations = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateArray) object:nil];
[queue addOperation:operations];
[operations release];
}
And loop function:
-(void)updateArray{
while (loop)
{
NSLog(#"start loop");
if(loop){
[NSThread sleepForTimeInterval:2.0];
NSLog(#"start send request");
NSURL *url1 = [NSURL URLWithString:#"http://server.com"];
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
NSLog(#" request sent");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
else
return;
}
}
And viewdisappear()
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
loop = NO;
delete=NO;
[operations cancel] ;
}
My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?
You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.
[self addObserver:self forKeyPath:#"loop" options:NSKeyValueObservingOptionNew context:nil];
Then you have to implement the changes as per requirement in the following method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
There are a couple of issues that leap out at me:
If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.
As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.
I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.
You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.
At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.
If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.
Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:
Define my properties:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, getter = isLooping) BOOL looping;
#property (nonatomic, weak) AFHTTPRequestOperation *operation;
Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:
- (void)viewDidAppear:(BOOL)animated
{
self.looping = YES;
[self scheduleRequestIfLooping];
}
- (void)viewWillDisappear:(BOOL)animated
{
self.looping = NO;
[self cancelScheduledRequest];
}
The methods that do the starting and stopping of the scheduled requests would use the NSTimer:
- (void)scheduleRequestIfLooping
{
if ([self isLooping]) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(initiateRequest:) userInfo:nil repeats:NO];
}
}
- (void)cancelScheduledRequest
{
[self.timer invalidate];
self.timer = nil;
// you might want to cancel any `AFHTTPRequestOperation`
// currently in progress, too
[self.operation cancel];
}
Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.
Finally, put the scheduling of the next request inside the completion block of the current request.
- (void)initiateRequest:(NSTimer *)timer
{
// create AFHTTPRequestOperation
AFHTTPRequestOperation operation = ...
// now schedule next request _after_ this one, by initiating that request in the completion block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
[self scheduleRequestIfLooping];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
// not sure if you want to schedule request if last one failed
}];
self.operation = operation; // save this in the property
}
In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.

Dispatch queues, concurrency and completion handling

I have an array of objects to be processed. The objects have a method like below
#interface CustomObject : NSObject
- (void)processWithCompletionBlock:(void (^)(BOOL success))completionBlock;
#end
The processing of each object takes various time and can have different results. And it is known that the processing itself is executing concurrently. To say the truth it would be great to limit the number of concurrent operations because they are pretty intensive.
So I need to enumerate this array of objects and process them. If some object processing fails I need to skip all the rest objects. And of course I need to be notified after all objects will be enumerated and processed.
Should it be solved by the creation of NSOperationQueue and NSOperation subclass? How this class could look to fulfill these requirements? Are there some other elegant approaches?
This is exactly what NSOperation is designed for. Dispatch queues are much lower-level handlers, and you'd have to construct many of the pieces you need for this. You can of course do that (NSOperationQueue is built on top of GCD), but you'd be reinventing NSOperation.
You can handle NSOperation two ways for the most part. If it's simple, you can just create an NSBlockOperation. If it's a bit more complex, you can subclass NSOperation and override the main method to do what you want.
There are several ways to cancel all the other operations. You could have a separate operation queue per group. Then you can easily call cancelAllOperations to shut down everything. Or you could have a separate controller that knows the list of related operations and it could call cancel on them.
Remember that "cancel" just means "don't schedule if it hasn't stared, and set isCancelled if it has." It doesn't abort a running operation. If you want to abort a running operation, the operation needs to periodically check isCancelled.
You typically should limit the number of concurrent operations the queue will run. Use setMaximimumConcurrentOperationCount:.
There are two ways to determine that all the operations are finished. You can make an extra operation (usually a BlockOperation) and use addDependency: to make it depend on all the other operations. That's a nice asynchronous solution. If you can handle a synchronous solution, then you can use waitUntilAllOperationsAreFinished. I typically prefer the former.
Use NSOperationQueue and make your Class an NSOperation
Use this method to queue your work
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
Add a reference to the operation queue to the NSOperation subclass you create
If an error occurs call
- (void)setSuspended:(BOOL)suspend
on NSOperationQueue
Ok. To help others to understand how this approach can be handled I am sharing my own code.
To limit the number of concurrent threads we can call the -(void)setMaximimumConcurrentOperationCount: method of NSOperationQueue instance.
To iterate objects and provide the completion mechanism we can define the following method:
- (void)enumerateObjects:(NSArray *)objects
{
// define the completion block
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"Update UI");
}];
// register dependencies
for (CustomObject *obj in objects) {
CustomOperation *operation = [[CustomOperation alloc] initWithCustomObject:obj];
[completionOperation addDependency:operation]; // set dependencies for the completion block
[_operationQueue addOperation:operation];
}
// register completionOperation on main queue to avoid the cancellation
[[NSOperationQueue mainQueue] addOperation:completionOperation];
}
Overwrite the - (void)start method of the NSOperation subclass to start our custom operation:
- (void)start
{
// We need access to the operation queue for canceling other operations if the process fails
_operationQueue = [NSOperationQueue currentQueue];
if ([self isCancelled]) {
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self willChangeValueForKey:#"isExecuting"];
// We do not need thread detaching because our `-(void)processWithCompletionBlock:` method already uses dispatch_async
[self main]; // [NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
Overwrite the - (void)main method of the NSOperation subclass to process our custom object:
- (void)main
{
#try {
NSLog(#"Processing object %#", _customObject);
[_customObject processWithCompletionBlock:^(BOOL success) {
_processed = success;
if (!success) {
NSLog(#"Cancelling other operations");
[_operationQueue cancelAllOperations];
}
[self completeOperation];
}];
}
#catch (NSException *exception) {
NSLog(#"Exception raised, %#", exception);
}
}
Thanx to #Rob for pointing me out to the missing part.

Removing observers for an NSOperation

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];
}

NSOperation KVO problem

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).

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.