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.
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 GCD in my app to fetch lot of images and data in the background queue and when I present uiimagepickercontroller it takes more time to show the camera preview. After googling around, I found that many have faced this issue with iOS 7 and here is one more post that made some sense to me. (iOS 7 UIImagePickerController Camera No Image). The solution was to stop the background threads and then present the picker controller. But I am really stumped and don't know how to stop or pause the background threads, present the picker controller and then resume/start the background thread. Can someone please help me with this.
Here is how I am doing my background fetching of images and data in my networking class (like everyone else). My serial queue is initialized like this.
sharedInstance.serialQueue = dispatch_queue_create("user-detail-queue", DISPATCH_QUEUE_CONCURRENT);
And the code to fetch things in the background is like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *myUserID = [JNKeychain loadValueForKey:#"userID"];
NSString *sessionToken = [JNKeychain loadValueForKey:#"sessionToken"];
if(sessionToken && myUserID)
{
dispatch_async([BGNetworkAPI sharedInstance].serialQueue, ^{
id object = [[BGNetworkAPI sharedInstance].userCache objectForKey:myUserID];
if(object)
{
NSDictionary *cachedResponseDictionary = (NSDictionary *)object;
BGUser *user = [BGUser createUserWithResponseDictionary:cachedResponseDictionary];
if(block)
{
block(user, nil);
}
}
else
{
[[BGUserManagementClient sharedClient] fetchUserDetailsWithUserId:myUserID withSessionToken:sessionToken withSuccessBlock:^(AFHTTPRequestOperation *operation, id responseObject)
{
//NSLog(#"The response object of my user object is %#",responseObject);
[[BGNetworkAPI sharedInstance].userCache setObject:responseObject forKey:myUserID];
NSDictionary *responseDictionary = (NSDictionary *)responseObject;
BGUser *user = [BGUser createUserWithResponseDictionary:responseDictionary];
if(block)
{
block(user,nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"The error while fetching the user details is %#", operation.responseObject);
if(block)
{
block(nil, error);
}
}];
}
}
});
With the code above you are not doing the fetching in the backgroud as dispatch_get_main_queue()gives you the queue the interface is running on. To perform a background fetch you have to open another queue with dispatch_queue_create("image_queue", NULL).
When the backgroud fetch is finished and you want to do something with the UI you have to do this in the main queue.
dispatch_async(dispatch_get_main_queue(),^{
if(block) {
block(user,nil)
}
});
After playing around with the code, I figured out the problem. In all the places where I am doing background fetch, I was sending the completion block just like that. When I changed the returning of the completion block in the main block, it started to work properly. Thanks a lot to #HackingOther to point out my mistake.
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.
I have a NSOperation that I put in a queue. The NSOperation does some long running photo processing then I save the information/meta data in core data for that photo. In the main method of my custom NSOperation class is where I execute the below block of code
-(void)main
{
//CODE ABOVE HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
etc...
} completion:^(BOOL success, NSError *error) {
NSLog(#"Done saving");
}];
}
My issue is that even with only 3 photos when it saves it really freezes my UI. I would have thought executing this in the NSOperation I would be fine.
I should add that each NSOperation processes one photo, so at times the queue could have 5-10 photos, but I would not think this would make any difference, even with just three like I said its freezing the UI.
Thank you for the help.
UPDATE:------------*--------------
I switched to version 2.2 but that seems to be blocking the UI even more...also now I'm using
-(void)main
{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
//CODE BELOW HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
[localContext saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
}];
}
This is all done in my NSOperation class, am I doing something wrong?
Don't put the saveWithBlock calls in a background thread. You're effectively creating a background thread from a background thread, which, in this case, is just slowing you down. You should just be able to call saveWithBlock and it should put all your saving code in the background. However, I'm also noticed that you make all your changes in the main UI page of the code, and only call save afterward. This is the wrong usage of this method. You want to do something more like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//photo processing
//update post from photo processing
} completion:^(BOOL success, NSError *error) {
//This is called when data is in the store, and is called on the main thread
}];
If you do need an NSOperation, I suggest a different pattern:
- (void) main {
NSManagedObjectContext *localContext = [NSManagedObjectContext confinementContext];
// Do your photo stuff here
Post *post = [Post createInContext:localContext];
//more stuff to update post object
[localContext saveToPersistentStoreAndWait];
}
Be careful in how you start the operation.
[operation start]
will start the operation on the current thread, so if you call it from the main thread (which is the UI one) it will block the interface.
You should add the operation to a queue, so that it runs in background without hogging the main thread
[[NSOperationQueue new] addOperation:operation];