AFNetworking happening on the main thread - ios

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.

Related

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 background JSON parsing avoid block nesting

I have some networking code with heavy JSON parsing going on. It needs to be done in the background to not block the main thread. The code looks like this :
-(void) getSomeDataWithParameters:(...)parameters completion:(void (^)(NSArray *data))completion
{
NSURLRequest *req = ...
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:req];
// sometimes I have more requests
// startOperations is a wrapper on AFHTTPClient enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock:
// that handles errors and loading views
[self startOperations:#[op] completionBlock:^(NSArray *operations) {
// getBgQueue = return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(getBgQueue(), ^{
NSArray *data = [MyParserClass parseJSON:op.responseJSON inContext:self.localContext];
[self.localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
// this is executed on main thread
if(completion) completion(...);
}];
});
}];
}
(AFNetworking 1.x)
The above code works very fine, but it's a pain to setup and write. And often the whole method content is wrapped inside another block to fetch some required data first... basically the blocks just pile up and makes ugly code
I'm using enqueueBatchOfHTTPRequestOperations and not individual completion blocks on AFJSONRequestOperation because batch completion block would sometimes fire before all individual operations completion blocks... (I also read somewhere that Mattt discouraged doing this)
Any pointers on how to do better than this?
I'm not sure what you want here, but just like "longcat is long", it's somewhat inherent in the pattern: 'continuation-passing style is continuation-passing style'. If you want to flatten things out a bit, you could make local block variables, but to a certain degree, you're stuck because you need the completion for -MR_saveToPersistentStoreWithCompletion to close over data in order to pass it to the -getSomeDataWithParameters... completion, but data won't exist until the -startOperations completion is executed.
You could probably achieve a less-nested appearance by using a bunch of __block variables, and splitting the code into several local blocks, but to me that feels kind of like cutting off your nose to spite your face. This code is readily understandable the way it is.
By the way... I notice that you're closing over op in the -startOperations completion block. This is fine because you're enqueuing op by doing -startOperations: #[op] ... but it would arguably be cleaner to get op from the operations parameter to the completion. I tightened this up as much as seemed reasonable:
- (void)getSomeDataWithParameters:(...)parameters completion:(void (^)(NSArray *data))completion
{
NSURLRequest *req = ...;
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:req];
[self startOperations:#[op] completionBlock:^(NSArray *operations) {
for (AFJSONRequestOperation *op in operations) {
dispatch_async(getBgQueue(), ^{
NSArray *data = [MyParserClass parseJSON:op.responseJSON inContext:self.localContext];
void (^mrSaveCompletion)(BOOL, NSError*) = completion ? ^(BOOL success, NSError *error) { completion(data); } : nil;
[self.localContext MR_saveToPersistentStoreWithCompletion: mrSaveCompletion];
});
}
}];
}
This will fan out each response potentially to a different thread. If you want all responses to execute on a single background thread, just swap the nesting of the for loop and the dispatch_async.
From there, the only really "superfluous" code is the dispatch_async. You could eliminate that by making -startOperations:... take a queue parameter where you would pass in the queue you wanted the completion to be called. Maybe like this:
- (void)startOperations: (NSArray*)ops completionQueue: (dispatch_queue_t)queue completionBlock: (void (^)(NSArray*))completion
{
void (^completionWrapper)(NSArray*) = !completion ? nil : ^(NSArray* ops) {
if (queue)
dispatch_async(queue, ^{ completion(ops); });
else
completion(ops);
};
[self startOperations: ops completionBlock: completionWrapper];
}
- (void)getSomeDataWithParameters:(...)parameters completion:(void (^)(NSArray *data))completion
{
NSURLRequest *req = ...;
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:req];
[self startOperations:#[op] completionQueue: getBgQueue() completionBlock:^(NSArray *operations) {
for (AFJSONRequestOperation *op in operations) {
NSArray *data = [MyParserClass parseJSON:op.responseJSON inContext:self.localContext];
void (^mrSaveCompletion)(BOOL, NSError*) = !completion ? nil : ^(BOOL success, NSError *error) { completion(data); };
[self.localContext MR_saveToPersistentStoreWithCompletion: mrSaveCompletion];
});
}];
}

GCD stopping async queues from proceeding

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.

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