How to get completion block of NSOperationQueue [duplicate] - ios

This question already has answers here:
Get notification when NSOperationQueue finishes all tasks
(16 answers)
Closed 8 years ago.
How to get completion block of NSOperationQueue, here I want to spin activity indicator from start to end of all operation.
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// Set the max number of concurrent operations (threads)
[operationQueue setMaxConcurrentOperationCount:3];
[operationQueue addOperations:#[operation, operation1, operation3,...] waitUntilFinished:NO];
Thanks.

You need to implement KVO to observe.
Go for addDependency on operation which will help you to "isFinished key" of the operation, and all dependency are resolved it performs KVN. After that you can run your logic of spin activity indicator. Also you can write a block as well. Check the following code:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operationObj = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"Show your activity...");
}];
[operationObj setCompletionBlock:^{
NSLog(#"Operation has finished...");
}];
[queue addOperation: operationObj];
Check following references URL for it
Get notification when NSOperationQueue finishes all tasks
When will completionBlock be called for dependencies in NSOperation

Related

How to know all concurrent NSOperations are completed

My situation is:
I have users
Each user has some history data that can be fetched via user objects
What I want to do is:
Max 2 users must be fetching their history data at the same time (this is the reason that I want to use NSOperationQueue)
I need to get notified when any user finished fetching its history data
I need to get notified when every user finished fetching their history data
What I ask is:
How can I achieve what I want to do since I can't make it thru with the code below?
Any help is appreciated.
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:2];
for (User *user in users) {
NSBlockOperation *operationObject = [NSBlockOperation blockOperationWithBlock:^{
[user loadHistoryData:^{
[NSNotificationCenter postNotificationToMainThread:#"oneUserFetchedHistory"];
}];
}];
[operationQueue addOperation:operationObject];
}
This question differs from this question because I don't want to chain any requests. I just want to know when they are all finished executing.
This answer has a completion block for only one operation queue.
This answer offers a way to make operation block to wait until async call to loadHistoryData is completed. As writing setSuspended
I could not find an answer for my need. Is there any?
I've used AsyncBlockOperation and NSOperationQueue+CompletionBlock
Combining them is working for me like this:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
// 1 is serial, more is concurrent
queue.completionBlock = ^{
[NSNotificationCenter postNotificationToMainThread:#"allUsersFetchedHistory"];
};
for (User *user in users){
[queue addOperationWithAsyncBlock:^(AsyncBlockOperation *op) {
[user loadHistoryData:^{
[op complete];
[NSNotificationCenter postNotificationToMainThread:#"oneUserFetchedHistory"];
}];
}];
}

NSOperation fails on execution

I have problem with NSOperations. Everything works fine but sometimes (I don't know why) Operation block is simply skipped. Am I missing something? How is it possible that operation is not even NSLogging "operation entered"? Here is some code from viewDidLoad:
//I'm using weakOperation in order to make [self.queue cancelAllOperation] method when viewWillDisappear
NSBlockOperation* operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation* weakOperation = operation;
NSString *session=#"";
#try{
session = [self getSessionId];//getting data from CoreData
}#catch(NSException *e)
{
NSLog(#"EXCEPTION WITH SESSION");
}
weakOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"operation entered");
[self downloadJSONArray]; //doing some connection downloading and using session
[self downloadImages]; //downloading images from urls from JSONs
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self refresh]; //update mainThread
}
}
[self.queue addOperation:weakOperation];
What could be scenario that coul make skip this block ?
Is there max number of threads created in iOS?
EDIT: Hey, I'have found why this happends - when a lot of applications run in the background and iOS does not have resources to queue another thread it simply skips that, how to behave in this situation?
You are assigning a new NSBlockOperation to a weak variable. Whenever you assign a new object to a weak variable, you risk having it released immediately.
If you needed a weak reference to the operation, you'd assign the object to some local variable first, and then get the weak reference for that object:
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"operation entered");
[self downloadJSONArray]; //doing some connection downloading and using session
[self downloadImages]; //downloading images from urls from JSONs
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self refresh]; //update mainThread
}
}
__weak NSBlockOperation* weakOperation = operation;
[self.queue addOperation:weakOperation];
But, as the method stands, the weakOperation is unnecessary. You generally only need weak references to avoid strong reference cycles. But no such cycle is present currently, so you can just do:
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"operation entered");
[self downloadJSONArray]; //doing some connection downloading and using session
[self downloadImages]; //downloading images from urls from JSONs
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self refresh]; //update mainThread
}
}
[self.queue addOperation:operation];
Looking at your code comment, you say "I'm using weakOperation in order to make [self.queue cancelAllOperation] method when viewWillDisappear". Using weakOperation like this will not accomplish what you want because your operation is not checking to see if it was canceled and thus it will not respond when the NSOperationQueue tries to cancel it.
If you wanted to do that, then a variation on your weakOperation pattern can be useful, but rather than using this weakOperation to add it to the queue, you can use the weak reference within the block to check to see if the operation was canceled (and you want the weak reference in the block to avoid the block from retaining the operation, itself, causing a strong reference cycle). The other key observation is that rather than creating a new NSBlockOperation, simply add an execution block to the original operation you created:
NSBlockOperation* operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation* weakOperation = operation;
[operation addExecutionBlock:^{
NSLog(#"operation entered");
if ([weakOperation isCancelled]) return;
[self downloadJSONArray]; //doing some connection downloading and using session
if ([weakOperation isCancelled]) return;
[self downloadImages]; //downloading images from urls from JSONs
if ([weakOperation isCancelled]) return;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self refresh]; //update mainThread
}];
}];
[self.queue addOperation:operation];
Clearly, if the operation is tied up in downloadJSONArray or downloadImages, it won't respond to the cancelation event until it returns from those methods. You'd have to check the cancelation status with those methods, too, if you want this operation to respond reasonably quickly to the cancellation event.
In answer to your second question, yes, there is a maximum number of threads, but it's a reasonably large number and there are other factors that come into play before the number of threads becomes an issue. The constraining factor is likely to be the downloadImages method (as you can only have 5 concurrent download requests). And even if that wasn't an issue, you'd want to constrain the number of concurrent operations, anyway, to mitigate the app's peak memory usage. If there are any network operations involved, you generally want to do something like:
self.queue.maxConcurrentOperationCount = 4; // or 5
That way, you minimize how much of the limited system resources (including threads) you are using.
By the way, I assume that downloadJSONArray and downloadImages are synchronous methods. If those are performing asynchronous network requests, you might want to consider further refactoring of the code to ensure the operation doesn't complete prematurely (e.g. wrap this in a concurrent NSOperation subclass or change those methods to run synchronously).

Send a bunch of requests one-by-one [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I need to send 100 network requests to my server one-by-one and get notified when the 100th is done.
I'm using AFNetworking and was thinking about a solution of this problem. Can anyone recommend me something?
A couple of thoughts:
If really just going to run each request serially (i.e. one after another), you could do:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All operations done");
}];
for (NSInteger i = 0; i < operationCount; i++) {
AFHTTPRequestOperation *operation = ... // create your operation here
[completionOperation addDependency:operation];
[queue addOperation:operation];
}
[queue addOperation:completionOperation];
Note, using operation queue like this offers the advantage that you can easily cancel all the operations in that queue should you ever need to.
If the order that these are performed is critical, you might want to establish explicit dependencies between the operations, e.g.:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All operations done");
}];
NSOperation *priorOperation = nil;
for (NSInteger i = 0; i < operationCount; i++) {
AFHTTPRequestOperation *operation = ... // create your operation here
[completionOperation addDependency:operation];
if (priorOperation) [operation addDependency:priorOperation];
[queue addOperation:operation];
priorOperation = operation;
}
[queue addOperation:completionOperation];
The question for me is whether you absolutely only want to run one at a time. You pay a significant performance penalty for that. Generally you'd use that first code sample (where the only explicit dependencies are to the completion operation) and set maxConcurrentOperationCount to something like 4, enjoying concurrency and its consequent significant performance gain (while at the same time, constraining the degree of concurrency to some reasonable number that won't use up all of your worker threads, risk having requests time out, etc.).
You haven't said what these 100 operations are, but if it's a bunch of downloads, you might want to consider a "lazy loading" pattern, loading the data asynchronously as you need it, rather than all at once.
If downloading images, for example, you might achieve this using the AFNetworking UIImageView category.
This is a specific form of a common question, which is "how do I call a sequence of block operations and get notified when the last one finishes?"
One idea is to make a "to-do list" using the parameters for each request. Say each request takes a number 0..99. Now pseudo-code would looks like this:
#property(nonatomic, copy) void (^done)(BOOL); // we'll need to save a completion block
#property(nonatomic, strong) NSMutableArray *todo; // might as well save this too
- (void)makeRequestsThenInvoke:(void (^)(BOOL))done {
self.todo = [NSMutableArray arrayWithArray:#[#99, #98, #97 ... #0]];
// make this in a loop using real params to your network request (whatever distinguishes each request)
self.done = done;
[self makeRequests];
}
- (void)makeRequests {
if (!self.todo.count) { // nothing todo? then we're done
self.done(YES);
self.done = nil; // avoid caller-side retain cycle
return;
}
// otherwise, get the next item todo
NSNumber *param = [self.todo lastObject];
// build a url with param, e.g. http://myservice.com/request?param=%# <- param goes there
[afManager post:url success:success:^(AFHTTPRequestOperation *operation, id responseObject) {
// handle the result
// now update the todo list
[self.todo removeLastObject];
// call ourself to do more, but use performSelector so we don't wind up the stack
[self performSelector:#selector(makeRequests) withObject:nil afterDelay:0.0];
}];
}

Cancelling an NSOperation from another NSOperation

I've got an NSOperation queue, and four NSOperations which run in it.
NSOperationQueue myQueue = [[NSOperationQueue alloc] init];
NSOperation readOperation = [[NSOperation alloc] init];
NSOperation postOperation = [[NSOperation alloc] init];
NSOperation deleteOperation = [[NSOperation alloc] init];
I'm aware a cancel can be called an NSOperation object. If I call a
[postOperation cancel];
does it get cancelled immediately from myQueue?
Also I would like to cancel the deleteOperation from the postOperation.
Does this work?
postOperation = [NSBlockOperation blockOperationWithBlock: ^{
[deleteOperation cancel];
/**** do a HTTP post ****/
}];
[myQueue addOperation:postOperation];
Essentially I want to cancel a delete operation before I do the POST, if if that operation was executing. Also does
[myQueue setMaxConcurrentOperationCount:1];
ensure that the operation queue is FIFO?
Per NSOperation documentation:
... if an operation is in a queue but waiting on unfinished dependent operations, those operations are subsequently ignored. ... allows the operation queue to call the operation’s start method sooner and clear the object out of the queue.
the queue will call the operation's start methods immediately which should then mark it as finished without doing any useful work.
Note that you could override this method is subclasses. Apple asks you to create the same behavior as in NSOperation, but it's still up to the developer.
does [myQueue setMaxConcurrentOperationCount:1]; ensure that the operation queue is FIFO?
That's a separate question. The answer is no. You don't have control over order of operations other then setting dependencies (which is what you should be doing).

How can I extend an NSOperationQueue with dependencies for appDidEnterBackground?

I know how to extend a task for running in the background after an iOS app enters background with
beginBackgroundTaskWithExpirationHandler
dispatch_async
etc.
But what if I have an NSOperationQueue that I want to extend as background tasks, without losing the interdependencies of the NSOperations? Say I have this:
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// Do stuff
}];
NSBlockOperation *op2a = [NSBlockOperation blockOperationWithBlock:^{
// Do stuff
}];
[op2a addDependency:op1];
NSBlockOperation *op2b = [NSBlockOperation blockOperationWithBlock:^{
// Do stuff
}];
[op2b addDependency:op1];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
// Do stuff
}];
[op3 addDependency:op2a];
[op3 addDependency:op2b];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations: #[op1, op2a, op2b, op3] ];
Is there an elegant way to have the NSOperationQueue finish in the background?
I realized that I didn't fully understand how background thread extension works.
After calling beginBackgroundTaskWithExpirationHandler to start the background extension, I can do whatever I want in the background. I thought there is just one thread extended, but it's in fact the whole application that keeps running.
Therefore I just have to call endBackgroundTask at the end of the last NSOperation to achieve what I want.

Resources