I want to use success block in background. I use code:
[manager POST:URLString parameters:params success:^(AFHTTPRequestOperation *responseHeader, id responseBody) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
[self parseResponse:responseHeader and:responseBody forRequest:request];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"FAIL request: %#", error);
}];
In
[self parseResponse:responseHeader and:responseBody forRequest:request];
I send NSNotification to update UI. But it not working...why?
Notifications are sent synchronously and on the same thread when you post them so you need to switch back to the main thread, preferably before posting (though you could switch after receiving the callback).
You can use either dispatch_async or performSelector:onMainThread: to switch to the main thread.
UI Operations must be ran on the main thread.
When performed on a background thread - You will get a warning in the debugger about this and the update will happen a few seconds later (when the UI thread will actually get the call).
This is why you must use dispatch_async with the main thread to pop back to the main thread and perform all UI operations on that thread.
Do note - in AFNetworking - there are async load operations that happen on another thread anyhow and the completion blocks return on the same queue as they went out of - so in theory - if all you do is update UI - then you shouldn't handle it in your code too.
Related
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.
My app fetches some items from web server like below:
for (photo in photoList) {
NSArray *comment = [self fetchCommentsFromServer:photo.photoId];
[photo setComment:comment];
}
fetchCommentFromServer makes asynchronous http call with dispatch_async.
dispatch_queue_t queue = dispatch_queue_create("autantication_queue", 0);
dispatch_async( queue, ^{
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure) {
failure(error);
}
}
];
});
It gives error because of comment isn't ready when I try to attach it to photo.
How can I guarantee that the comment is ready when it's attached to photo?
I tried to use semaphores but it made the call too slow.
fetchCommentsFromServer needs a completion block. This can be executed whenever the network call has finished. Alternatively, you can work on each photo after the fetch is complete, as part of the network call completion block.
The only way you can guarantee the network fetch is finished when you want to work on the results is to not try and do any work until the network fetch is finished. This is pretty much the point of all these completion blocks and delegate methods.
The method you call to get the data executes then the next line sets the message without the response coming back. You should modify fetchCommentsFromServer to have a completion block where you then set the comment inside the block (ensuring the request has completed before trying to modify it).
On a side note, make sure to jump back on the main thread to modify any UI elements (AKA your label).
fetchCommentsFromServer: can't return anything because the data that it wants to return isn't available until after the method has completed. Instead, the method should take a completion block as a parameters and pass the data back using that. In your case, this will be called from the AFNetworking success block.
I am using the following code to invoke the [self.tableView reloadData] method inside the dispatch_async on the main thread. It works fine and as expected.
-(void) setup
{
_genres = [NSMutableArray array];
[[MyAppClient sharedClient] getGenres:^(NSURLSessionDataTask *task, id responseObject) {
[responseObject enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Genre *genre = [[Genre alloc] initWithDictionary:obj];
[_genres addObject:genre];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
Even if I use it without the dispatch_async call the UITableView reloads just fine. My question is that if there anyway benefit of dispatch_async in the above scenario.
My reason is that since I am updating the UI which runs on the main thread and that is why I am using dispatch_async(main_queue,block)
You always want to make sure that you do UI updates on the main thread, so you are right about doing the dispatch_async. One thing about your code, though: you're doing the dispatch_async to reload the table inside the block, so it is doing it for every single execution of the block. You only need to do it once, so I would suggest moving the dispatch_async to below the call to enumerateObjectsUsingBlock. This also ensures that you're not updating _genres on the background thread while the main thread is getting it to update the table.
You must update the UI on the main thread. If the response block is called on a background thread then your use of dispatch_async(dispatch_get_main_queue()... is correct as well as required.
I'm trying to use CFRunLoopRunInMode() to avoid returning in [AFHTTPClient getPath:...] completion blocks.
My code looks like:
NSLog(#"start");
__block BOOL someCondition = NO;
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://domain.com"]];
[client getPath:#"my/path" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"async log");
someCondition = YES;
});
while (!someCondition) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, YES);
}
NSLog(#"failure");
}];
And I expected the output to be:
start
async log
failure
But instead I only get:
start
CFRunLoopRunInMode() returns kCFRunLoopRunHandledSource, but the dispatch queue never executes the submitted block. If I run the same code outside the completion blocks, the output is as expected.
I cannot figure out why the dispatch queue is not processed when run from completion blocks.
Can someone please throw some light on why this happens?
I cannot figure out why the dispatch queue is not processed when run from completion blocks.
Because you didn't "run" the dispatch queue (there is no such thing as "running" a dispatch queue). You ran the run loop. The dispatch queue is related, but is a different thing. Only one block can run at a time on a serial queue (like main). Until your block completes, no other block can be scheduled. There is no API into GCD to circumvent that. This is generally a very good thing because it gives certainty to queue operations that do not always exist on runloop operations.
AFNetwork's success and failure block both scheduled in the main queue, the async block you write in the failure block is also scheduled in the main queue.
Until failure block completed, your GCD sync block will run. Because the 'while' condition will never be false, failure block will never complete and it will never has the chance to be executed.
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.