NSOperation cancel and NSOperationQueue - ios

I would like to understand what is the correct behavior for an NSOperation subclass.
I have my subclasses with different isReady conditions. Yes, I check in the code if the operation is cancelled and I act in consequence. This is great while the operation is in execution. It stops its task, sets finished to true and it gets deleted from the queue. But what about its dependencies? They are not yet executing, so they stay in the NSOperationQueue in cancelled state forever.
Should I set ready = true for cancelled operations so the queue will call the start method that will set in executing and immediately finish the task setting finished to true?

The dependent operation will be executed. No matter a is cancelled or not.
You should set operation.finished==true at the final state to make NSOperationQueue removes the operation.
You should set .ready==true whenever it's ready to begin the operation.
In life cycle of operation, you should frequently check .cancelled whenever .cancelled == true, you should stop operation and set .finished == true.

I just dealt with this problem myself. I could not understand why when I cancelled my NSOperation subclass, it was never removed from the NSOperationQueue. It turns out that my overridden isReady method was to blame. The isReady method was checking the super implementation AND whether a property had been set, which it hadn't, which is why the operation was getting cancelled. However, apparently if an NSOperation instance never reaches the ready state, even if it is cancelled, the NSOperationQueue will not remove that operation until is has reached the ready state. Hence, the solution is to OR your custom conditions for readiness with the isCancelled property value. Alternatively, you can change the logic such that the operation will always be ready, but do nothing in the main method if your conditions for readiness are not met, depending on your application logic.
Example:
// for proper KVO compliance
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keys = [super keyPathsForValuesAffectingValueForKey:key];
NSString *readyKey = NSStringFromSelector(#selector(isReady));
if ([readyKey isEqualToString:key]) {
keys = [keys setByAddingObjectsFromArray:#[NSStringFromSelector(#selector(url)), NSStringFromSelector(#selector(isCancelled))]];
}
return keys;
}
- (BOOL)isReady
{
return (this.url != nil || this.isCancelled) && super.isReady;
}
- (void)setUrl:(NSURL *)url
{
if (self.url == url) {
return;
}
NSString *urlKey = NSStringFromSelector(#selector(url));
[self willChangeValueForKey:urlKey];
_url = url;
[self didChangeValueForKey:urlKey];
}
Thanks to NSHipster's KVO article for the above KVO-related code.

Related

Completion block is getting triggered even before my operation completes in main method

I am trying to create user in firebase using OperationQueue and Operation. I placed the Firebase Auth call in operation main method. Completion block of the operation is getting triggered even before firebase registration process is succeeded.
RegistrationViewModal.swift
//This is operation initialization
let operationQueues = OperationQueues()
let registrationRecord = RegistrationRecord(user: self.user!, encryptedData: self.fireBaseAuthCompliance)
let userRegistrationOperation = UserRegistrationOperation(registrationRecord: registrationRecord)
userRegistrationOperation.completionBlock = {
//I am expecting this completion block will be called only when my firebase invocation in main() method is finished
DispatchQueue.main.async {
//Since this block is getting triggered even before completion, the //value is returning as null
self.user?.uid = userRegistrationOperation.registrationRecord.user.uid
}
}
operationQueues.userRegistrationQueue.addOperation(userRegistrationOperation)
UserRegistrationOperation.swift
class OperationQueues {
lazy var userRegistrationQueue: OperationQueue = {
var queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.name = "User registration queue"
return queue
}()
}
class UserRegistrationOperation: Operation {
var registrationRecord: RegistrationRecord
init(registrationRecord: RegistrationRecord) {
self.registrationRecord = registrationRecord
}
override func main() {
guard !isCancelled else { return }
self.registrationRecord.state = RegistrationStatus.pending
//Firebase invocation to create a user in Firebase Auth
Auth.auth().createUser(withEmail: self.registrationRecord.user.userEmail, password: self.registrationRecord.encryptedData){ [weak self](result, error) in
if error != nil {
print("Error occured while user registration process")
self?.registrationRecord.state = RegistrationStatus.failed
return
}
self?.registrationRecord.user.uid = result?.user.uid
self?.registrationRecord.state = RegistrationStatus.processed
}
}
}
The problem is that your operation is initiating an asynchronous process, but the operation finishes when the asynchronous task is started, not when the asynchronous task finishes.
You need to do the KVO associated with a “concurrent” operation, as outlined in the documentation:
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
start()
isAsynchronous
isExecuting
isFinished
In a concurrent operation, your start() method is responsible for starting the operation in an asynchronous manner. Whether you spawn a thread or call an asynchronous function, you do it from this method. Upon starting the operation, your start() method should also update the execution state of the operation as reported by the isExecuting property. You do this by sending out KVO notifications for the isExecuting key path, which lets interested clients know that the operation is now running. Your isExecuting property must also provide the status in a thread-safe manner.
Upon completion or cancellation of its task, your concurrent operation object must generate KVO notifications for both the isExecuting and isFinished key paths to mark the final change of state for your operation. (In the case of cancellation, it is still important to update the isFinished key path, even if the operation did not completely finish its task. Queued operations must report that they are finished before they can be removed from a queue.) In addition to generating KVO notifications, your overrides of the isExecuting and isFinished properties should also continue to report accurate values based on the state of your operation.
Now all of that sounds quite hairy, but it’s actually not that bad. One way is to write a base operation class that takes care of all of this KVO stuff, and this this answer outlines one example implementation.
Then you can subclass AsynchronousOperation instead, and make sure to call finish (or whatever triggers the isFinished KVO) when the task is done:
class UserRegistrationOperation: AsynchronousOperation {
var registrationRecord: RegistrationRecord
init(registrationRecord: RegistrationRecord) {
self.registrationRecord = registrationRecord
super.init() // whenever you subclass, remember to call `super`
}
override func main() {
self.registrationRecord.state = .pending
//Firebase invocation to create a user in Firebase Auth
Auth.auth().createUser(withEmail: registrationRecord.user.userEmail, password: registrationRecord.encryptedData) { [weak self] result, error in
defer { self?.finish() } // make sure to call `finish` regardless of how we leave this closure
guard let result = result, error == nil else {
print("Error occured while user registration process")
self?.registrationRecord.state = .failed
return
}
self?.registrationRecord.user.uid = result.user.uid
self?.registrationRecord.state = .processed
}
}
}
There are lots of ways to implement that AsynchronousOperation class and this is just one example. But once you have a class that nicely encapsulates the concurrent operation KVO, you can subclass it and you can write your own concurrent operations with very little changes to your code.

Function cannot run concurrently

I would like to make a function that only run once and cancel if is still running.
I tried it using a simple lock boolean on start/end, but sometimes it's "overlapping".
There's a better and secure way to do that?
#property (assign) BOOL lock;
- (void)myFuntion
{
if (self.lock) {
NSLog(#"(Canceled) Syncing is already running...");
return;
}
self.lock = YES;
// My Code
self.lock = NO;
}
The NSLock class should be able to help you here. I have not tried this example directly, but something like:
NSLock *myFunctionLock=[NSLock new]; // this should be a class data member/property/etc.
- (void)myFuntion
{
if (![myFunctionLock tryLock])
return; /* already running */
// My Synchronized Code
[myFunctionLock unlock];
}
We are all assuming that you are talking about concurrent programming, where you are running the same code on different threads. If that's not what you mean then you would need to explain what you DO mean, since code that runs on the same thread can only execute a function once at any particular moment.
Take a look at NSLock's tryLock function. The first caller to assert the lock gets back a TRUE, and can proceed to access the critical resource. Other callers get back FALSE and should not access the critical resource, but won't block.

NSOperation and NSOperationQueue

I am concurrently downloading some information from server and I am using NSOperatioQueue for the same. I have an issue. For instance if a download operation fails for some reason I don't want to remove that operation from queue.
Right now even if its a failure as soon as it gets a response back from server the operation is removed from queue.
Is there any way to tell the queue that a particular operation is not logically finished and it should keep it in queue?
In my case, I am downloading a set of information. For example fetching all places in a County and then all houses for each county. So in certain cases county cannot be downloaded if the user is not logged in with a valid token. In that case server returns a failure message. I want to keep such items in queue so that I can try again when user logs in to the app.
Sample Code
self.downloadQueue.maxConcurrentOperationCount = 1;
for(Campaign *campaign in campaigns)
{
isContentUpdated = false;
if(self.operation)
self.operation = Nil;
self.operation = [[DownloadOutlets alloc] initWithCampaign:campaign];
[self.downloadQueue addOperation:operation];
}
where downloadQueue is an NSOperationQueue and DownloadOutlets extends NSOperation.
Thanks
You should not be keeping your failed operations in the queue. The failed operation has performed its task. You should have your operation controller listen to the state of the operations, via completionBlock or otherwise, and decide what to do next. If it comes to the determination that the operation has failed but a similar operation should be retried, it should add another operation to perform the task again.
Another approach would be to retry your download inside the operation until success, and only then end the operation. This is not optimal design, however, because the operation does not, and should not, have all the information required to decide whether to retry, inform the user, etc.
You shouldn't keep operations that failed in queue, but use the queue for serial fetching data, and stop queueing if the operation fails :
#implementation DataAdapter
// ...
-(void)setup{
// weak reference to self to avoid retain cycle
__weak DataAdapter* selfRef= self;
// create a block that will run inside the operation queue
void(^pullCountriesBlock)(void)= ^{
[[DownloadManager instance] fetchAllCountriesWithCompletionBlock:^(Result* result){
if(result.successful){
// on success
[selfRef didFetchDataForAction:action];
}else{
// on failure
[selfRef failedToFetchDataForAction:action];
}
};
self.actions= [NSMutableArray new];
[self.actions addObject:[DownloadAction actionWithBlock:pullCountriesBlock];
// add other actions
// ...
[self fetchData];
}
}
-(void)fetchData{
if(self.currentActionIndex >= self.actions.count){
[self finishedFetchingData];
return;
}
[self fetchDataForAction: self.actions[self.currentActionIndex] ];
}
-(void)fetchDataForAction:(DownloadAction*)action
[self.myOperationQueueImplementation enqueueOperationWithBlock:action.block];
}
If the download is successful, just enqueue the next action(increment the currentActionIndex and call fetchData). If it fails, you can act accordingly. What I'd do is start listening to interesting NSNotificationCenter events before calling fetchData the first time. You could listen to UserDidLogInNotification or any other that may allow the queue to continue running the downloads.

Serializing asynchronous tasks in objective C

I wanted to be able to serialize 'genuinely' async methods, for example:
making a web request
showing a UIAlertView
This is typically a tricky business and most samples of serial queues show a 'sleep' in an NSBlockOperation's block. This doesn't work, because the operation is only complete when the callback happens.
I've had a go at implementing this by subclassing NSOperation, here's the most interesting bits of the implementation:
+ (MYOperation *)operationWithBlock:(CompleteBlock)block
{
MYOperation *operation = [[MYOperation alloc] init];
operation.block = block;
return operation;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
if (self.block) {
self.block(self);
}
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (BOOL)isFinished
{
return self.finished;
}
- (BOOL) isExecuting
{
return self.executing;
}
This works well, here's a demonstration...
NSOperationQueue *q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 1;
dispatch_queue_t queue = dispatch_queue_create("1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("2", NULL);
MYOperation *op = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"1...");
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"1");
[o finish]; // this signals we're done
});
}];
MYOperation *op2 = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"2...");
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"2");
[o finish]; // this signals we're done
});
}];
[q addOperations:#[op, op2] waitUntilFinished:YES];
[NSThread sleepForTimeInterval:5];
Note, I also used a sleep but made sure these were executing in background thread to simulate a network call. The log reads as follows
1...
1
2...
2
Which is as desired. What is wrong with this approach? Are there any caveats I should be aware of?
"Serializing" asynchronous tasks will be named actually "continuation" (see also this wiki article Continuation.
Suppose, your tasks can be defined as an asynchronous function/method with a completion handler whose parameter is the eventual result of the asynchronous task, e.g.:
typedef void(^completion_handler_t)(id result);
-(void) webRequestWithCompletion:(completion_handler_t)completionHandler;
-(void) showAlertViewWithResult:(id)result completion:(completion_handler_t)completionHandler;
Having blocks available, a "continuation" can be easily accomplished through invoking the next asynchronous task from within the previous task's completion block:
- (void) foo
{
[self webRequestWithCompletion:^(id result) {
[self showAlertViewWithResult:result completion:^(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
}
}
}
Note that method foo gets "infected by "asynchrony" ;)
That is, here the eventual effect of the method foo, namely printing the user's answer to the console, is in fact again asynchronous.
However, "chaining" multiple asynchronous tasks, that is, "continuing" multiple asynchronous tasks, may become quickly unwieldy:
Implementing "continuation" with completion blocks will increment the indentation for each task's completion handler. Furthermore, implementing a means to let the user cancel the tasks at any state, and also implement code to handle the error conditions, the code gets quickly confusing, difficult to write and difficult to understand.
A better approach to implement "continuation", as well as cancellation and error handling, is using a concept of Futures or Promises. A Future or Promise represents the eventual result of the asynchronous task. Basically, this is just a different approach to "signal the eventual result" to the call site.
In Objective-C a "Promise" can be implemented as an ordinary class. There are third party libraries which implement a "Promise". The following code is using a particular implementation, RXPromise.
When utilizing such a Promise, you would define your tasks as follows:
-(Promise*) webRequestWithCompletion;
-(Promise*) showAlertViewWithResult:(id)result;
Note: there is no completion handler.
With a Promise, the "result" of the asynchronous task will be obtained via a "success" or an "error" handler which will be "registered" with a then property of the promise. Either the success or the error handler gets called by the task when it completes: when it finishes successfully, the success handler will be called passing its result to the parameter result of the success handler. Otherwise, when the task fails, it passes the reason to the error handler - usually an NSError object.
The basic usage of a Promise is as follows:
Promise* promise = [self asyncTasks];
// register handler blocks with "then":
Promise* handlerPromise = promise.then( <success handler block>, <error handler block> );
The success handler block has a parameter result of type id. The error handler block has a parameter of type NSError.
Note that the statement promise.then(...) returns itself a promise which represents the result of either handler, which get called when the "parent" promise has been resolved with either success or error. A handler's return value may be either an "immediate result" (some object) or an "eventual result" - represented as a Promise object.
A commented sample of the OP's problem is shown in the following code snippet (including sophisticated error handling):
- (void) foo
{
[self webRequestWithCompletion] // returns a "Promise" object which has a property "then"
// when the task finished, then:
.then(^id(id result) {
// on succeess:
// param "result" is the result of method "webRequestWithCompletion"
return [self showAlertViewWithResult:result]; // note: returns a promise
}, nil /*error handler not defined, fall through to the next defined error handler */ )
// when either of the previous handler finished, then:
.then(^id(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
return nil; // handler's result not used, thus nil.
}, nil)
// when either of the previous handler finished, then:
.then(nil /*success handler not defined*/,
^id(NEError* error) {
// on error
// Error handler. Last error handler catches all errors.
// That is, either a web request error or perhaps the user cancelled (which results in rejecting the promise with a "User Cancelled" error)
return nil; // result of this error handler not used anywhere.
});
}
The code certainly requires more explanation. For a detailed and a more comprehensive description, and how one can accomplish cancellation at any point in time, you may take a look at the RXPromise library - an Objective-C class which implements a "Promise". Disclosure: I'm the author of RXPromise library.
At a first glance this would work, some parts are missing to have a "proper" NSOperation subclass though.
You do not cope with the 'cancelled' state, you should check isCancelled in start, and not start if this returns YES ("responding to the cancel command")
And the isConcurrent method needs to be overridden too, but maybe you omitted that for brevity.
When subclassing NSOperation I would strongly suggest only overriding main unless you really know what you are doing as it is really easy to mess up thread safety. While the documentation says that the operation will not be concurrent the act of running them through an NSOperationQueue automatically makes them concurrent by running them on a separate thread. The non-concurrency note only applies if you call the start method of the NSOperation yourself. You can verify this by noting the thread ID that each NSLog line contains. For example:
2013-09-17 22:49:07.779 AppNameGoesHere[58156:ThreadIDGoesHere] Your log message goes here.
The benefit of overriding main means that you don't have to deal with thread safety when changing the state of the operation NSOperation handles all of that for you. The main thing that is serializing your code is the line that sets maxConcurrentOperationCount to 1. This means each operation in the queue will wait for the next to run (all of them will run on a random thread as determined by the NSOperationQueue). The act of calling dispatch_async inside each operation also triggers yet another thread.
If you are dead set on using subclassing NSOperation then only override main, otherwise I would suggest using NSBlockOperation which seems like what you are somewhat replicating here. Really though I would avoid NSOperation altogether, the API is starting to show its age and is very easy to get wrong. As an alternative I would suggest something like RXPromise or my own attempt at solving this problem, FranticApparatus.

How can I wait for a NSURLConnection delegate to finish before executing the next statement?

This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}

Resources