I am using NSOperation in my application.
I am cancelling the previously executing operation when create another operation. But the previously created operation's dealloc method not calling when cancelling that operation.
Pls suggest me.
Thanks.
I think what you need is that isFinished returns YES and isExecuting returns NO after cancelled. Otherwise the operation object will never be released.
Document says.
In addition to simply exiting when an operation is cancelled, it is
also important that you move a cancelled operation to the appropriate
final state. Specifically, if you manage the values for the isFinished
and isExecuting properties yourself (perhaps because you are
implementing a concurrent operation), you must update those variables
accordingly. Specifically, you must change the value returned by
isFinished to YES and the value returned by isExecuting to NO. You
must make these changes even if the operation was cancelled before it
started executing.
That's just fine:
Responding to the Cancel Command
Once you add an operation to a queue, the operation is out of your
hands. The queue takes over and handles the scheduling of that task.
However, if you decide later that you do not want to execute the
operation after all—because the user pressed a cancel button in a
progress panel or quit the application, for example—you can cancel the
operation to prevent it from consuming CPU time needlessly. You do
this by calling the cancel method of the operation object itself or by
calling the cancelAllOperations method of the NSOperationQueue class.
Canceling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed. The default
implementation of NSOperation does include checks for cancellation.
For example, if you cancel an operation before its start method is
called, the start method exits without starting the task.
The dealloc method will be called when the retain count of the object gets to zero alas when no other object is using it.
Related
I am reading docs on OperationQueue.
I have this doubt, what is the sync and async status of the operation, BlockOperation and OperationQueue we are creating.
As I am understanding it, it will run as sync operation, but in case we want to run it in async manner we have to dispatch it it in async manner using dispatch.async.
Am I understanding it right?
Synchronous operation will be seen as completed by OperationQueue when the block you submit to BlockOperation returns (or main method if you subclass Operation)
Asynchronous operation (that is returning true from its isAsynchronous property), must be marked as finished manually in a subclass of Operation by setting isFinished = true (you should also set isExecuting = false at the same time). This allows you to dispatch whatever work you need to do to a different queue by using Dispatch.async, but still keep the operation in OperationQueue. This is useful to build dependant operations or to allow only certain amount of operations to run in parallel.
Apple's Operation docs have good explanations for all of this.
I have an NSOperation and in the completionblock I do some time consuming saving to CoreData. The actual operation runs fast. Occasionally I need to prevent this completetionblock from running. I can't see an easy way to cancel it so I tried setting a BOOl and skipping the code within it if necessary. However the operation is run within a singleton and another class needs to run the queue (that's why I need to cancel the completionblock). Any ideas on how I can cancel the completeionblock?
You can simply set completionBlock to nil:
operation.completionBlock = nil
But I think that cancelling operation's completion block from another class might be a design smell. You can end up with code which is hard to maintain and debug, as it's not clear why and when the operation's flow was changed.
An NSOperation that runs fast with a time consuming completion block seems wrong. NSOperation can be cancelled, that's its main purpose. blocks cannot be cancelled unless you do all the work itself.
I'd suggest creating a subclass of NSOperation which does all the work, including what is now done in the completion block, and the code that is now in the completion block can check that it is cancelled.
On the other hand, "saving to CoreData" doesn't seem to be something you can just cancel half way through, so the better suggestion might be to take a step back and figure out what you actually want to happen.
You can cancel the operation by calling cancel method of NSOperation.
[operation cancel];
Many long running async methods have completion handler blocks attached as input parameters to them
I'm not sure if the completion handler should be called if the operation was cancelled.
-(void)longRunningAsyncOperation:(Input *)input completionHandler:(Block)completionHandler
{
// long running code
// periodic checks for cancelation
if(_canceled)
{
// should completion handler still be called?
return;
}
// more long running code
// completed
completionHandler(someData);
}
I don't think there's necessarily one "right answer" here. You should just make it do whatever you need to do. One option, as #Fogmeister proposed in a comment is to make the completion routine take an argument indicating whether it was canceled or completed normally. It would seem advisable to have something called in all cases so that an interested party can know that the operation was canceled.
I've seen other APIs that take two different completion blocks -- a "success" block and a "failure" block. To my mind, a single block that takes arguments to indicate status seems like a more adaptable pattern.
If you don't call any completion block on cancelation then there is effectively "lost information"; absent some other mechanism, it's impossible for the outside world to know that the operation was canceled, so it seems like one of these patterns, be it an argument to the completion, or success/failure blocks, would be preferable to simply not calling anything.
#ipmcc is correct. There is generally no one right answer, but best practices generally dictates that you should always call the completion block and, if the completion block actually cares, pass it a success/cancelled flag. The reason this is a best practice is that some memory may have been allocated as a prelude to the cancelled operation and if you don't call the completion handler, you'll never have the opportunity to free it again.
I would say yes, it shall call the completion handler.
The rationale for this is, that one can view a completion handler as some form of return value.
So, not calling the completion handler is like not returning a value in a function which is declared to return something.
2 part question but related so will keep in the same thread:
I'm adding NSOperations to a NSOperationQueue. I need to know when the operation will start and when it ends in order to update the UI.
I thought about adding a "start handler" block to run in the nsoperation as well as a "completion handler" in the NSOperation
Something like
-(id)initOperationWithStartBlock:(StartBlock)startblock completionBlock:(CompletionBlock)completionBlock
but believe that there is a better way to get this from the queue itself.
How can this be done?
I would also like to know the index of the job sent by the NSOperationQueue.
I've tried doing
[[self.myQueue operations] indexForObject:operation]
but the index is always the zeroth index - because the completed jobs were removed from the nsoperationqueue array before I could check the jobs index.
Any way to preserve them?
You need to use Key-Value-Observing pattern in IOS. So for this you need to setup observers in your controller to look for changes to isFinished and isExecuting to catch start and finish hooks.
It depends if you want to perform something from within your object upon starting or elsewhere in your code. From what you are saying (you want to update the UI), this sounds like you want to act outside of your object, but I don't know your program. You have two options:
1) If you want to act in your object upon starting the operation from within the same object, use key-value observation and observe isExecuting with self as the observer and the observed. Don't forget that you will get called whether it goes from NO to YES (starting) or YES to NO (done).
2) If you want to perform an action outside of the object, I would rather recommend to use the very general NSNotification with NSNotificationCenter and within your main, post a notification such as #"willStart" and #"didComplete". In any other object, register as an observer for your notifications.
Either way, don't forget that notifications are sent in the current threads but the UI must be updated on the main thread. You don't know on what thread observe:keyPath: is called. You may need to call performSelectorOnMainThread to update the UI or you can even use the convenient and useful nsoperationqueue mainqueue with a addOperationWithBlock with your UI code. If you use the NotificationCenter, then you can simply yourself post on the main thread with nsobject performSelectorOnMainThread
I am using NSOperation to perform some heavy parsing of data, then return back to the main thread with objects ready to be used by my app. I handle all operations by placing them on a singleton NSOperationQueue. I do this to control how much processing is happening at any point, because each operation temporarily uses a pretty high memory footprint.
So, I have a scenario where I can have several view controllers on screen. Each view controller will create a parsing operation on load and add it to the queue. I allow 2 concurrent processing operations by setting the maxConcurrentOperationCount. Each view controller creates a processing operation, places it on the singleton queue, and retains the operation as a property so it has a handle on it.
If the view controller needs to go away in response to a user initiated Delete action, I use the NSOperation property in the dealloc method of my view controller to cancel the operation:
-(void)dealloc{
[self.currentOperation cancel];
[super dealloc];
}
In my NSOperation subclass, I check the isCancelled property in several places (mostly before significant chunks of long running work) the isCancelled property and attempt to respond to it:
if([self isCancelled]){
// Perform cleanup
return;
}
The problem is that the isCancelled property is evaluating to false and the operation continues, eventually calling into Core Data to attempt to retrieve data that has been deleted. I see this happen even when I place an isCancelled check immediately prior to the Core Data fetch request.
I've got a workaround to keep the app from crashing, but I'm thinking I might be going about the implementation wrong. Is there any other way I can maintain a handle on the operation while it is processing so I can cancel it if needed? Is my method not retaining the proper handle on the operation and preventing it from being properly cancelled?
You can't do logic like that in dealloc.
First, dealloc must call super's dealloc as the last line. Once that is done, the object is gone and all subsequent messaging behaviour is undefined ( will crash).
It is also very likely that the queue will retain the operation, thus making cancellation in dealloc meaningless because dealloc cannot be called until the queue releases (unless your memory management is screwed up).
You need to separate your cancellation/invalidation logic from memory management entirely.