NSOperations, dependencies and failed operations - ios

I've started working with CloudKit and finally started using subclassed NSOperation for most of my async stuff.
How ever, I have two questions.
How can I mark an operation as failed? That is, if operation A fails, I don't wan't its dependent operations to run. Can I just not mark it as isFinished? What happens to the unexecuted items already in the queue?
What would be the recommended route to take if I would like something like a try, catch, finally. The end goal is to have one last operation that can display some UI with information of success or report errors back to the user?

isFinished means your operations complete execution, you can cancel an operation but that means your operation is canceled and that could be done without even executing the operation and you can check that by calling isCanceled, if you want spefically failure and success flags after executing an NSOperation then in subclass add isFailure property and check in dependent operation before executing, and cancel that if isFailure is set to true.
You can add dependency on operation and check there status, and if all are successful just update UI on main thread or report and error.
Update
You can keep and array of dependent operations and when on failure you can cancel those operations.

Add the operations to a queue. When one of them fails call cancel on the queue. Future CKOperations will error on start with "operation was cancelled before it started" and any block operations won't even run.
One way is to use KVO on the queue's operationCount (Example on Github) and wait until it is zero. Then if you got an error at any stage (and captured it) you can make a final callback operation ended with the error. However, usually you will end up wanting to display a different message depending on the operation that caused the error, in which case it is best to handle them when they happen, rather than wait until the end when you then need to figure out which error came from which operation.

Overview:
When an operation fails (based on your business logic) and you want to abort all dependant operations then you can cancel dependant operations manually
Order in which you cancel operation is important as cancelling an operation would allow for the dependant operations to start (as the dependancy condition has been broken).
In order to all this you need to have variables holding each of those dependant operations, so that you cancel them in the order you intend.
Code:
var number = 0
let queue = OperationQueue()
let b = BlockOperation()
let c = BlockOperation()
let d = BlockOperation()
let a = BlockOperation {
if number == 1 {
//Assume number should not be 1
//If number is 1, then it is considered as a failure
//Cancel the remaining operations manually in the reverse order
//Reverse order is important because if you cancelled b first, it would start c
d.cancel()
c.cancel()
b.cancel()
}
}
b.addDependency(a)
c.addDependency(b)
d.addDependency(c)
queue.addOperation(a)
queue.addOperation(b)
queue.addOperation(c)
queue.addOperation(d)

Related

Why we need the synchronous operation in ios

I want to know As we all know how asynchronous task are necessary for concurrency but Wanted to know why we need the synchronous tasks. while we can achieve the same with the normal usage of function.
Thanks & regards
Rohit
When you calls something synchronously, it means that 'the thread that initiated that operation will wait for the task to finish before
continuing'. Asynchronous means that it will not wait for finish the task.
synchronous calls stops your current action and returns when the call returned. with asynchronous calls you can continue.
synchronous is the opposite of asynchronous code, and therefore is ordinary code.
At the end, if asynchronous is totally out of scope then you will not emphasize the word synchronous.
It helps to synchronise threads, as the name suggests.
consider a typical usage of GCD async and sync (pseudo)
async background_thread {
//1 call webservice or other long task that would block the main thread
sync main_thread {
//2 update UI with results from 1
}
//3 do something else that relies on 2
}
now if 2 was in an async and you needed to do something at 3 that relies on the updates at 2 to have happened, then you are not guaranteed (and most likely wont) get the behaviour you are expecting. instead, you use a sync to make sure that the task is completed before continuing the execution in the background thread.
If you are asking now, why not just take out the sync/async around 2 so it executes in order anyway? the problem is, the UI must not be updated on a background thread otherwise the behaviour is undefined (which usually means the UI lags a lot). So in essence what happens is the background thread waits at 2's sync until the main thread gets round to executing that block, then it will continue with the rest of the execution on the background thread.
If you were dealing with a task that doesnt require the main thread (or some other thread) to execute properly, then yes you may as well take out the sync at 2.
This is just one example of how a sync is useful, there are others if you are doing advanced threading in your app.
Hope this helps
Typically it's because you want to do an operation on a specific different thread but you need the result of that operation. You cannot do the operation asynchronously because your code will proceed before the operation on the other thread completes.
Apple has a very nice example:
func asset() -> AVAsset? {
var theAsset : AVAsset!
self.assetQueue.sync {
theAsset = self.getAssetInternal().copy() as! AVAsset
}
return theAsset
}
Any thread might call the asset method; but to avoid problems with shared data, we require that only functions that are executed from a particular queue (self.assetQueue) may touch an AVAsset, so when we call getAssetInternal we do it on self.assetQueue. But we also need the result returned by our call to getAssetInternal; hence the call to sync rather than async.

NSOperationQueue - running synchronously

I need to make some API calls and I want to ensure that they come back in the order that they went out. Is this the proper flow to have that happen?
Create NSOperationQueue, set max concurrent operations to 1
Create URL String to API
Create NSOperation block, call method to call API, pass URL string
Add NSOperation to NSOperationQueue
This is where I get confused. Setting the max concurrent operations to 1 essentially makes NSOperationQueue into a synchronous queue, only 1 operation gets called at a time. However, each operation is going to make a NSURLSession call, which is async. How can I ensure that the next operation doesn't run until I have finished with the first? (By finish I want to store the returned JSON in a NSArray, adding each additional returned JSON to that array).
The proper way to ensure that NSOperations run in order is to add dependencies. Dependencies are powerful as they allow ordering of different operations on different queues. You can make an API call or data processing on a background queue; when complete, a dependent operation can update the UI on the main thread.
let operation1 = NSBlockOperation {
print("Run First - API Call")
}
let operation2 = NSBlockOperation {
print("Run Second - Update UI")
}
operation2.addDependency(operation1)
let backgroundQueue = NSOperationQueue()
backgroundQueue.addOperation(operation1)
NSOperationQueue.mainQueue().addOperation(operation2)
// operation1 will finish before operation2 is called, regardless of what queue they're in
See Apple Docs on addDependency in NSOperation here: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/index.html#//apple_ref/occ/cl/NSOperation
Also, be careful with assuming that maxConcurrentOperationCount = 1 as all that it does is ensure that only 1 operation will run at a time. This does NOT ensure the order of the queue. An operation with a higher priority will likely run first.

Is it possible to cancel nsoperation without access to it?

I need to use a library which is not woking properly. After calling some method it creates some NSOperation, which never finishes and ends up with sleeping state mach_msg_trap error and as because I don't have the instance of these operations, I'm unable to cancel it.
Is it possible to cancel it without access?
Note that even if you had access to the NSOperation or its queue, "cancel" doesn't mean "make go away." It just means "don't start if it hasn't been started yet, and if it has been started, set the cancel flag." If the NSOperation doesn't check its cancel flag, then it still won't terminate. There's no way to do what you're trying to do, and this is intentional. Forcibly terminating an operation would leave the program in an undefined state.
It sounds like you either have a bug in the library, or a bug in how you're using the library (I would suspect the latter before the former, but it depends on the library). You're going to need to track down that bug and resolve it.
Do you have access to the NSOperationQueue where these Operations have been added?
If yes, you can access like this:
var queue = NSOperationQueue.mainQueue()
// or
queue = NSOperationQueue.currentQueue()!
let operations = queue.operations
for operation in operations{
if operation.name == "name of operation if you know it" {
operation.cancel()
}
}

iOS using NSOperationQueue and AFNetworking to implement multi-download feature

I am using NSOperationQueue and AFNetworking to implement a multi-download feature for my project. I also use Core Data to persist downloaded information but I am OK with that part. My problem is when it comes to actually create the download tasks. So here is how I did that:
// create a NSOperationQueue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"DownloadQueue";
queue.maxConcurrentOperationCount = 1;
// create an operation (AFDownloadRequestOperation is a subclass of AFHTTPRequestOperation)
AFDownloadRequestOperation *operation = // some initializations
// add the operation to the queue
[queue addOperation:operation];
// later some more operations are created and added to the queue
Then say I added 3 operations to the queue. Since I set the maxConcurrentOperationCount to 1, so only 1 operation in the queue is running, the other 2 are waiting:
operation 1: running
operation 2: waiting
operation 3: waiting
Then I use pause(which is a method of AFURLConnectionOperation) to pause the operation that is running, and I expect to see one of the 2 waiting operations start running, but it does not happen, the 2 waiting operations remain waiting:
operation 1: paused
operation 2: waiting
operation 3: waiting
I still have to resume operation 1 and wait till it finishes, then operation 2 or 3 will start running.
My question is how to make the other operations in the queue running after I pause a running operation like I explained above?
Also, I would appreciate it very much if you could explain a better way to implement multi-downloading (specifically related to manage the max number of download tasks, pause/resume a download task)
Solution:
Simply use cancel to pause an operation (You will need to handle the situation when the cancel flag is "YES" in your operation to actually let the operation quit). Then all the operations in the queue behaviors as I expected (cancel one operation, then another waiting one gets to run)
And when you need to resume downloading, simply create a new operation that fetches contents from web to the same local file path. (with the help of AFDownloadRequestOperation, resuming downloading works well)
Now my multi downloading system works well, comment if you need info about implementation.

Do NSOperations and their completionBlocks run concurrently?

I've got a bunch of NSOperations added to a NSOperationQueue. The operation queue has the maxConcurrentOperationCount set to 1, so that the NSOperations run one after the other.
Now, in the completionBlock of a NSOperation I want to cancel all pending NSOperations by calling cancelAllOperations on the NSOperationQueue.
Is it safe to do this? Can I be sure that the start-method of the next operation is called only after the completionBlock of the previous operation has been fully executed? Or do the completionBlock of the previous operation and the task of the current operation run concurrently?
The reason why I'm asking: I use AFNetworking to execute a batch of AFHTTPRequestOperations and want to perform one request only if all previous requests of the batch were successful.
My findings below no longer seem to be true. I've re-run the tests on iOS 8 and iOS 9 and the completion block of an operation always runs concurrently with the next operation. Currently, I don't see a way to make an operation wait for the previous completion block to finish.
I just tried this scenario in a sample project. Here is the result:
If the NSOperationQueue's maxConcurrentOperationCount is set to 1, an NSOperation's completionBlock and the next NSOperation in the queue run simultaneously.
But, if every NSOperation is linked to its previous operation by calling addDependency:, the execution of an operation waits until the previous operation's completionBlock has finished.
So, if you want to cancel the next operation in the completionBlock of the current operation and be sure that it is cancelled before it is started, you have to set dependencies between the NSOperations by calling addDependency:
NSOperation establishes dependency only based on the completion states of operations, and not on the results of completed operations.
However, most of the scenarios that I encounter are such that, the execution of operations depend not only on the completion of some other operations, but also based on the results obtained from the completed operations.
I ended up doing like the below method, but still exploring if there is a better way:
1) Operation-A runs
2) Operation-A compeletes and its completionBlock runs
3) In the OperationA's completion block, check for the result obtained from Operation-A.
If result is X, create Operation-B and add to the queue.
If result is Y, create Operation-C and add to the queue.
If result is error, create Operation-D (usually an alert operation) and add to the queue
So, this ends up as a sequence of operations, that are dynamically added to the queue, depending on the result of completed operations.
I came up with another seemingly better way to ensure that an operaion is executed only if certain conditions (based on the results of previously finished operations) are met, else, the operation is cancelled.
One important consideration here is that the condition check for running an operation should not be coded inside the operation subclass, thus allowing the operation subclass to be poratble across different scenarios and apps.
Solution:
- Have a condition block property inside the subclass, and set whatever condition form where the operation is instantiated.
- Override "isReady" getter of the NSOperation subclass, check the condition there, and thus determine if its ready for execution.
- If [super isReady] is YES, which means the dependent operations are all finished, then evaluate the necessary condition.
- If the condition check is passed, return YES. Else, set isCancelled to YES and return YES for isReady
Code:
In the interface file have the block property:
typedef BOOL(^ConditionBlock)(void);
#property (copy) ConditionBlock conditionBlock;
In the implementation, override isReady, and cancelled:
#implementation ConditionalOperation
- (BOOL)isReady {
if([super isReady]) {
if(self.conditionBlock) {
if(!self.conditionBlock()) {
[self setCancelled:YES];
}
return YES;
} else {
return YES;
}
} else {
return NO;
}
}

Resources