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.
Related
I'm maintaining an old game code (>5 yrs old) and switched developers hands a few times. Game doesn't has a dedicated player base (an early casino gambling game).
RestKit is used for API calls.
Please find comments: // SECTION_1 // SECTION_2 in the code below.
// SECTION_1 : can make it async, use blocking logic. What are the some immediate risks related to introducing threading bugs?
// SECTION_2 : Need to fix a bug bug in previous logic here. Bug: self.fetchAllPlayersCallback gets invoked before waiting for self.fetchAllPlayersFriendCheckCallback. For correct UI update, I would need to combine self.fetchAllPlayersFriendCheckCallback and self.fetchAllPlayersCallback.
Code:
/* getAllPlayersInGame:(NSString *)gameId
* Fetch players for a game in progress, update UI, invoke fetchAllPlayersCallback
* Also detect if players are friends. Prepare friends set and invoke fetchAllPlayersFriendCheckCallback.
*/
- (void)getAllPlayersInGame:(NSString *)gameId
{
self.fetchAllPlayersInProgress = YES;
self.fetchAllPlayersError = nil;
[SocialManager getPlayersAndProfilesForGameId:gameId userId:[UserManager getActiveUser] completion:^(NSError *error, SocialUsers *users, SocialProfiles *profiles)
{
if (error) {
self.fetchAllPlayersError = error;
// TODO: show ui error alert
return;
}
__block NSUInteger totalusers = [self.lobby.players count];
__block BOOL isAllPlayersFriends = YES;
__block NSMutableSet *friendsInGame = [[NSMutableSet alloc] init]
// SECTION_1
// separate lightweight call to server per player.
// server implementation limitation doesn't allow sending bulk requests.
for (SocialUser *player in self.lobby.players) {
NSString *playerId = player.playerID;
[SocialManager isUser:userId friendsWithPlayer:playerId completionBlock:^(PlayHistory *playHistory, NSError *error) {
totalusers--;
if (!error) {
isAllPlayersFriends &= playHistory.isFriend;
if (playHistory.isFriend)
{
// TODO: Add to friendsInGame
// TODO: save other details (game history, etc for ui population)
}
} else {
self.fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
return;
}
if (0 == totalusers) {
fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
}
}];
};
// SECTION_2
// TODO: update data model
// TODO: UI update view
self.fetchAllPlayersInProgress = NO;
if (self.fetchAllPlayersCallback)
{
self.fetchAllPlayersCallback();
self.fetchAllPlayersCallback = nil;
}
}];
}
There are a few approaches:
If you have a bunch of asynchronous requests that can happen concurrently with respect to each other and you want to trigger some other task when they're done, you might use Grand Central Dispatch (GCD) dispatch groups.
For example, rather than counting down totalUsers, the standard GCD approach is to use a dispatch group. Dispatch groups can trigger some block that will be called when a bunch of asynchronous calls are done. So you:
Create a group before you start your loop;
Enter your group before you start asynchronous call;
Leave your group in the asynchronous call's completion handler;
Specify a dispatch_group_notify block that will be called when each "enter" is matched with a "leave".
Thus, something like:
dispatch_group_t group = dispatch_group_create();
for (SocialUser *player in self.lobby.players) {
dispatch_group_enter(group);
[SocialManager ...: ^{
...
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
self.fetchAllPlayersInProgress = NO;
if (self.fetchAllPlayersCallback) {
self.fetchAllPlayersCallback();
self.fetchAllPlayersCallback = nil;
}
});
Now, this presumes that this call is asynchronous but that they can run concurrently with respect to each other.
Now, if these asynchronous calls need to be called consecutively (rather than concurrently), then you might wrap them in asynchronous NSOperation or something like that, which assures that even if they're running asynchronously with respect to the main queue, they'll run consecutively with respect to each other. And if you use that approach, rather than using a dispatch group for the completion operations, you would use NSOperation dependencies. For example, here's a trivial example:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSOperation *completion = [NSBlockOperation blockOperationWithBlock:^{
// stuff to be done when everything else is done
}];
for (Foo *foo in self.foobars) {
NSOperation *operation = [SocialManager operationForSomeTask:...];
[completionOperation addDependency:operation];
[queue addOperation:operation];
}
[[NSOperationQueue mainQueue] addOperation:completionOperation];
But all of this assumes that you're refactored your social manager to wrap its asynchronous requests in custom asynchronous NSOperation subclass. It's not rocket science, but if you haven't done that before, you might want to gain familiarity with creating them before you tackle refactoring your existing code to do so.
Another permutation of the previous point is that rather than refactoring your code to use custom asynchronous NSOperation subclasses, you could consider a framework like PromiseKit. It still requires you to refactor your code, but it has patterns that let you wrap your asynchronous task in "promises" (aka "futures"). I only mention it for the take of completeness. But you might not want to throw a whole new framework in this mix.
Bottom line, there's simply not enough here to diagnose this. But dispatch groups or custom asynchronous NSOperation subclasses with completion operations.
But the comment in that code that says "use blocking logic" is generally not a good idea. You should never block and with well designed code, it's completely unnecessary.
I am creating a serial queue in which i add two task as shown below
dispatch_queue_t serial = dispatch_queue_create("com.apple.serial", DISPATCH_QUEUE_SERIAL);
**//Task 1**
dispatch_async(serial, ^{
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
}];
});
**//Task 2**
dispatch_async(serial, ^{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
});
Inside that two task i am calling web service with NSURLSession. Problem is that before my Task 1 completion handle Task2 completion handle get called. According to theory by using serial queue each task waits for the previous task to finish before being executed. It my understanding is correct.
NSURLSession's already run on a background thread, so the issue you are seeing here is that as far as your serial queue is concerned once you call 'getUserProfileData:' technically the work for that block in your queue is finished because the NSURLSession is running on a different thread. If your main goal here is to simply call your second task after your first one completes I don't think you need your own queue you would probably be better off simply doing something like:
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
[self getUserTransactions];
}];
-(void)getUserTransactions
{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
}
EDIT:
If you are looking for something a little more robust I would check out this post for how you can subclass NSOperation to make your own Asynchronous Operation which you can then use with an NSOperationQueue.
Needed some advice/review on possible downsides of using a dispatch group inside another group, if it could lead to a race condition/deadlock or just wrong practice.
1) Can a dispatch_group_enter exist inside the scope of another group? I could not find an example from Apple following such practice. Remember, secondCall needs to happen after firstCall. There is a dependency. Thoughts?
2) What would be a good design to execute a thirdCall - which again depends on result of firstCall result. But agnostic of the completionHandler timing i.e. can happen later and doesn't need to wait for completionHandler to finish.
Here's a simplified example of the completion handler incorporating 3 calls -
-(void)someMethod:(void (^)(NSError *error))completionHandler {
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_enter(serviceGroup);
__typeof__(self) __weak weakSelf = self;
[self.obj firstCall completion:^(NSError *firstError) {
__typeof__(self) strongSelf = weakSelf;
// Second Call
if (!firstError.code) {
dispatch_group_enter(serviceGroup);
[strongSelf.obj secondCall completion:^(void) {
dispatch_group_leave(serviceGroup);
}];
}
// Third call
if (!firstError.code) {
[strongSelf executeThirdCall];
}
dispatch_group_leave(serviceGroup);
}]; // Closing block for first call.
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
if (completionHandler) {
completionHandler(error);
}
});
}
Some classic examples of dispatch groups can be found in this answer.
I can't think of any issues with this code.
However I am not sure you need dispatch groups at all for this example.
You are executing three requests. Request 2 and Request 3 both depend on the result of the Request 1. You need to call the function's completionHandler when Request 2 is finished. Can't you do it into the completion handler of Request 2?
I'm having a problem when I want to execute a code inside my dispatch_after block.
First of all, I'm calling a UIActivityIndicator when a button is pressed in order to show it in screen and after the uiactivityindicator starts runnning I want to execute a server call, when I get a response from the server I return that value.
The problem is: When I call my UIAtivityIndicator to run and after that I make my server call, the UIActivityIndicator doesn't show in screen even when the [UIActivityIndicatorInstance startAnimating]; was called and after that the server operation was called.
So I decided to use a dispatch_after in order to wait a certain time after de [UIActivityIndicatorInstance startAnimating]; It works whe I do this, the problem becomes when I have to return the value, so for that reason a use dispatch_semaphore to tell me when the operation has finished and then return the value.
The big problem here is that the dispatch_after is not called.
This is my code, I appreciate you can help me with this problem or some other solution you have in mind.
The main idea that I want to accomplish is that I want to show an UIActivityIndicator while the server operation is executing and when it finishes I want to return that value in the same method.
- (BOOL)getUserSatatus {
// This is when the UIActivityIndicator is starts running
[Tools startActivityIndicator];
double delayInSeconds = 0.5;
// This is used to save server response.
__block BOOL serverResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t executionTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
// I want to execute the server call after a perios of time in order to show first de indicator on screen
dispatch_after(executionTime, dispatch_get_main_queue(), ^{
NSLog(#"This is where the server will call");
// This is when I perform the service call and it returns a values that is
// assigned to server response.
serverResponse = [_backendManager getStatus];
// This is the signal for the semaphore in order to execute the next lines.
dispatch_semaphore_signal(semaphore);
});
// Wait until the signal in order to execute the next line.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return serverResponse; // Here will be the server return response.
}
You say:
The big problem here is that the dispatch_after is not called.
Yes, that's because you're blocking the main thread with dispatch_semaphore_wait, so the dispatch_after never has a chance to run on the main thread and you're deadlocking.
We can walk you through ways to get around this, but you really shouldn't have synchronous network calls or semaphores in your code at all (for a myriad of reasons, not just for your activity indicator and for solving your deadlocking issue).
You should remove these synchronous network requests, remove the dispatch_after, and remove the semaphores. If you do all of that, and instead follow asynchronous patterns (like using completion blocks), your activity indicator view stuff will then work properly and you won't have any deadlock either.
The correct answer is to refactor the "back end manager" to perform its requests asynchronously (with completion blocks) and then use completion block pattern with getUserStatus method, too.
For example, let's say you fixed getStatus of the _backendManager to behave asynchronously:
- (void)getStatusWithCompletion:(void (^)(BOOL))completion {
NSMutableURLRequest *request = ... // build the request however appropriate
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
BOOL status = ...; // parse the response however appropriate
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(status);
});
}];
[task resume];
}
Then you can refactor the getUserStatus from your question to also take a completion handler:
- (void)getUserStatusWithCompletion:(void (^)(BOOL))completion {
// This is when the UIActivityIndicator is starts running
[Tools startActivityIndicator];
[_backendManager getStatusWithCompletion:^(BOOL status){
[Tools stopActivityIndicator];
if (completion) completion(status);
}
}
And then the code that needs to get the user status would do something like:
[obj getUserStatusWithCompletion:^(BOOL success) {
// use `success` here
}];
// but not here
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.