GCD stopping async queues from proceeding - ios

I'm downloading a file using AFHTTPRequestOperation from the awesome AFNetworking.
I'm downloading large zip files, which need to be unzipped once the download is finished. But since the AFHTTPRequestOperation's completion block happens on the main thread, and these are relatively large files, I need to to the unzipping on a background thread, which I've implemented with GCD.
But the unzipping in this background thread can go wrong and if that's the case I need to be able to stop the thread from continuing... Is there a way to do it with GCD or do I have to put everything in the enormous body of an if statement?
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *theOperation, id responseObject){
NSLog(#"*** TP DOWNLOADER: Finished downloading");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Unzip the file:
ZipArchive *za = [[ZipArchive alloc] init];
BOOL unzipOpenFileSuccessful = [za UnzipOpenFile:operation.targetPath Password:#"thepassword"];
if(!unzipOpenFileSuccessful){
NSLog(#"Problems unzipping!");
//should return here and not keep doing work!
}
//keep doing work...
});
];

You can just "return" if an error occurs.

Related

BGTaskScheduler: How do you continue an operation in queue in the background?

I have set up all the steps required for adding the capability for background process (identified the task, registered and calling as instructed in this video).
I wondered if I'm downloading a file and before the download ends, the app goes to the background – what can I do to keep the download running? I just don't know about the last part in this method where I need to call on my last item in a queue and keep running the download.
The code shown is in Objective-C, but any hints in Swift would be extremely helpful as well.
- (void) handelBackgroudProcessingTaskForDownloads:(BGProcessingTask *) task {
[self scheduleAppBGProcessingTaskForDownloads];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
task.expirationHandler = ^{
[queue cancelAllOperations];
};
__weak NSOperation* lastOperation = MyDownloadService.shared.queue.operations.lastObject;
if (lastOperation != nil) {
lastOperation.completionBlock = ^{
[task setTaskCompletedWithSuccess: !lastOperation.isCancelled];
};
} else {
[task setTaskCompletedWithSuccess: !lastOperation.isCancelled];
}
//this is for the case that I have more than one file in the queue which they have not started downloading yet
if ((! lastOperation.isExecuting) && (! lastOperation.isCancelled) ) {
[queue addOperation:MyDownloadService.shared.queue.operations.lastObject];
}
// My question: How can I keep downloading the single file when app goes in bg before download is done? I tried this line below and did not work!
if(lastOperation.isExecuting) {
[MyDownloadService.shared.queue waitUntilAllOperationsAreFinished];
}

NSOperation inside setCompletionBlock another block handling

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

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.

AFNetworking happening on the main thread

I have a question with AFNetworking. I read the documentation here:
http://afnetworking.github.com/AFNetworking/Classes/AFImageRequestOperation.html
It says:
urlRequest:
The request object to be loaded asynchronously during execution of the operation.
Problem is when I execute the request for below, its happening on the main thread. I was able to confirm it by checking if it's in the main thread or not:
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:urlrequest success:^(UIImage *image) {
if ([NSThread mainThread]){
NSLog(#"this is the main thread");
}
}];
[operation start];
Is there anything I am doing incorrectly? Was I interpreting the documentation wrong? Why is it not asynchronous following the documentation?
KVISH >
However, you can assign a different successCallbackQueue :
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.successCallbackQueue = backgroundQueue;
The image is being loaded in the background and then your success block is being called on the main thread.
The point of this is that the loading of the image which takes some time and would block your UI is done on the background, but once the image is loaded you probably want to do something like set it on a UIImageView and that requires being on the main thread.
Wow, can't believe I didn't notice this myself before asking. In the AFNetworking classes, it has the below:
dispatch_async(requestOperation.successCallbackQueue ?: dispatch_get_main_queue(), ^(void) {
success(operation.request, operation.response, processedImage);
});
So the success is called on the main queue. Case closed.

Can one execute a completionBlock from ASIHTTPRequest/AFNetworking on a background thread?

I start my ASIHTTPrequest synchronously in a separate thread like this :
//inside this start method I create my ASIHTTPRequest and start it
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[process start];
});
But the completionBlock is still fired on the main thread. Can one keep the execution of the completionBlock in the same thread as where the request was started?
The only thing i can come up with to solve this is to define a dispatch queue and manually execute the completionBlock in the same thread, thus holding a reference to a created queue. But that does not solve it directly because you would pass for a freaking little moment in the main thread before redirecting the rest of the code to the created dispatch queue.
Does someone have a better solution?
EDIT: The same is also true for AFNetworking completion blocks...
Ok, to answer my own question :
ASIHTTPRequest framework does not have an option to start completion blocks in a different thread.
Instead one can use the AFNetwork framework. Here you have two properties on any type of AFOperation called 'successCallbackQueue' and 'failureCallbackQueue'. Where you can add a predefined 'dispatch_queue_t' to handle the execution of success and failure blocks.
Hope this will help others with the same problem!
UPDATE : Example
dispatch_queue_t requestQueue = dispatch_queue_create("requestQueue", NULL);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:yourRequest];
operation.successCallbackQueue = requestQueue;
operation.failureCallbackQueue = requestQueue;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// add code for completion
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// add code for failure
}];
[operation start];
Try using a defined queue of your own creation. Then you can (once it finally finishes, in its completion block) signal back to the global queue to update any display necessary.

Resources