NSOperation inside setCompletionBlock another block handling - ios

I have little tricky question :
NSOperation *operation = [[NSOperation alloc]init];
NSMutableDictionary * dictFileData = [arrData objectAtIndex:operationCounter];
[operation setCompletionBlock:^{
[self uploadFileOnAWSServer:[dictFileData valueForKey:#"MediaFileData"] MediaType:[[dictFileData valueForKey:#"MediaType"]integerValue] MediaKeyName:[dictFileData valueForKey:#"uploadKeyName"] MediaContentType:[dictFileData valueForKey:#"MediaContentType"]];
}];
[operationQueue addOperation:operation];
and for method uploadfileONAWSserve....
I have another block and its separate completion block.
In this way I have to upload multiple files on AWS server and once all files completion done I have to call service on my server.
Now I need to handle this complete scenario with multiple post with Success and failure cases.
Can anybody explain how can I handle it proper way with all success and failure cases.

Normally, you'd add dependencies to your operations:
NSOperation *uploadOperationA = // upload operation
NSOperation *uploadOperationB = // upload operation
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// do stuff when other operations complete
}];
[completionOperation addDependency:uploadOperationA];
[completionOperation addDependency:uploadOperationB];
However, you added the actual operation you want to perform in a completion block - and it is executed after the opearation's finished property is set to YES - and if an operation has dependencies, it is ready to execute when all the dependencies have finished set to YES. So in your case, the completion operation would execute before your uploads actually complete
Instead do:
NSOperation *uploadOperationA = [NSBlockOperation blockOperationWithBlock:^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self uploadFileOnAWSServer:[dictFileData valueForKey:#"MediaFileData"] MediaType:[[dictFileData valueForKey:#"MediaType"]integerValue] MediaKeyName:[dictFileData valueForKey:#"uploadKeyName"] MediaContentType:[dictFileData valueForKey:#"MediaContentType"]];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
}];
}];
dispatch_semaphore_t is necessary because, I assume, uploadFileOnAWSServer is asynchronous, and therefore will return immediately, before completing actual upload. In that case, somewhere in uploadFileOnAWSServer (perhaps completion block?) you should put
dispatch_semaphore_signal(sema);
If that function is not asynchronous, you can ditch the semaphore completely

Related

Objective-C: Waiting for the last call in method

I have a method that is being called several times. But I need to act only in the very last method call. I tried dispatch_async but didn't work because still is been queue the calls:
-(void)doingSomething:(NSString*)someValue
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:1.0f];
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the last call
});
});
}
Any of you knows a way to queue the calls and only use the very last call?
I'll really appreciate your help
My suggestion would be to use a dispatch_group. Call dispatch_group_enter before you call dispatch_async, and call dispatch_group_leave and the end of the block that's executed by dispatch_async. Then, after you've enqueued all the blocks, use dispatch_group_notify to schedule the completion block, which will run after all the other dispatch_async blocks have finished.
dispatch_group_t group = dispatch_group_create();
for (...) {
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
...
dispatch_group_leave(group);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// This gets called when all the other blocks have finished
});
Alternatively, you could use an NSOperationQueue instead of libdispatch, and make a completion operation which lists every other operation as a dependency. This does have the disadvantage that the completion operation won't be executed on the main queue, though.
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// This gets called when all the other operations have finished
}];
for (...) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
[completionOperation addDependency:operation];
[operationQueue addOperation:operation];
}
[operationQueue addOperation:completionOperation];

How to make api calls synchronously in background?

I have four api calls to make. They should be in following order:
apiSyncDataToCloud;
apiSyncImagesToServer;
apiDeleteDataFromCloud;
apiSyncDataFromCloudInBackground;
Each one of them is to be called irrespective of the fact that previous one finishes successfully or fails.
Also, each one of them have success and failure completion blocks.
In success completion block database is updated.
All this process has to be performed in background and has to be done a no of times.
Api calls are of course performed in background but once a call completes database update is performed on main thread thereby freezing the app.
So, I went with several solutions:
Tried following code:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
[self apiSyncDataToCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncImages];
}];
[queue addOperationWithBlock:^{
[self apiDeleteDataFromCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncDataFromCloudInBackground];
}];
But this only guarantees that api method calls will be performed in order. But their result follows no specific order. That is, method calls will be in the order specified but success block of apiSyncImagesToServer may be called before success block of apiSyncDataToCloud.
Then I went with following solution:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self apiSyncDataToCloud];
});
and in the success and failure blocks of apiSyncDataToCloud I have called apiSyncImagesToServer. This too did'nt work.
Now I am simply going with my last solution. I am just calling apiSyncDataToCloud.
In success completion block this method first updates the database and then calls other api.
In failure completion block this method simply makes the api call without updating the database.
For example-
structure of apiSyncDataToCloud is as follows:
-(void)apiSyncDataToCloud{
NSLog(#"method 1");
NSMutableDictionary *dicDataToBeSynced = [NSMutableDictionary dictionary];
dicDataToBeSynced = [self getDataToBeSynced];
if (dicDataToBeSynced.count!=0) {
if ([[StaticHelper sharedObject] isInternetConnected]) {
[[ApiHandler sharedObject] postRequestWithJsonString:API_SYNC_DATA_TO_CLOUD andHeader:[UserDefaults objectForKey:kAuthToken] forHeaderField:kAccessToken andParameters:dicDataToBeSynced WithSuccessBlock:^(NSURLResponse *response, id resultObject, NSError *error) {
NSLog(#"Data synced successfully to server");
[self updateColumnZSYNC_FLAGForAllTables];//updating db
[self apiSyncImagesToServer];//api call
} andFailureBlock:^(NSURLResponse *task, id resultObject, NSError *error) {
NSLog(#"Data syncing to cloud FAILED");
[self apiSyncImagesToServer];//simply make api call without updating db
}];
}
}else{
[self apiSyncImagesToServer];make api call even if no data to be synced found
}
}
Similary, inside apiSyncImagesToServer I am calling apiDeleteDataFromCloud.....
As a result my problem remained as it is. App freezes when it comes to success block updating db, downloading images...all operations being performed on main thread.
Plz let me know a cleaner and better solution.
You can create your own custom queue and call request one by one.
i.e.
dispatch_queue_t myQueue;//declare own queue
if (!myQueue) {//check if queue not exists
myQueue = dispatch_queue_create("com.queue1", NULL); //create queue
}
dispatch_async(myQueue, ^{[self YOUR_METHOD_NAME];});//call your method in queue block
If you want update some UI after receiving data then update UI on main Thread.
1) Better to use AFNetworking for this kind of situations. Because AFNetworking provides better way to handle Main & Background Threads.
AFNetworking supports success and failure blocks so you can do one by one WS Api calls from success and failure of previous WS Api call. So during this time period show progress HUD. Success of last API then update DB and hide progress HUD.
2) If you need to use NSOperationQueue and NSInvocationOperation
and follow this link. https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift
Api calls are of course performed in background but once a call
completes database update is performed on main thread thereby freezing
the app.
Then why not perform it in a separate queue?
Try using
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your code
});
to perform time-consuming tasks and
dispatch_async(dispatch_get_main_queue(), ^{
//your code
});
to only update UI.

Set up NSBlock operation that waits for async calls from other block operations to complete

I may be asking the impossible here, but how can I use NSOperationQueues to dispatch a number of asynchronous calls to HealthKit and only run a final operation when all those async calls have completed?
Here's how I make an array of block operations, each of which has an asynchronous call to HealthKit:
for( NSString *hkType in typesToRetrieve){
NSBlockOperation *newOp = [NSBlockOperation blockOperationWithBlock:^{
[self getAllStoredDataForHKQuantityIdentifier: hkType withCompletion:^(NSArray *results) {
[self processActivityResultsArray:results];
}];
}
}];
[loadingOperations addObject:newOp];
}
And here's how I add these operations:
for ( NSBlockOperation* blockOp in loadingOperations){
[endOperation addDependency:blockOp];
[retrievalOperationQueue addOperation:blockOp];
}
The problem is that endOperation is firing when the blocks have returned but not when their asycn calls have returned. Is there a clever way to keep things modular, as they are now, but wait for the async calls to return from the HealthKit queries?

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

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