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.
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.
I'm struggling to figure out the best method to test interacting with Core Data in a background thread. I have the following class method:
+ (void)fetchSomeJSON
{
// Download some json then parse it in the block
[[AFHTTPClient sharedClient] fetchAllThingsWithCompletion:^(id results, NSError *error) {
if ([results count] > 0) {
NSManagedObjectContext *backgroundContext = //... create a new context for background insertion
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(background, ^{ // If I comment this out, my test runs just fine
//... insert and update some entities
for (NSString *str in results) {
NSManagedObject *object = //...
}
});
}
}];
}
I'm currently testing this method with the following Kiwi code:
describe(#"MyAction", ^{
__block void (^completionBlock)(NSArray *array, NSError *error);
beforeEach(^{
// Stub the http client
id mockClient = [AFHTTPClient mock];
[WRNAPIClient stub:#selector(sharedClient) andReturn:mockClient];
// capture the block argument
KWCaptureSpy *spy = [mockClient captureArgument:#selector(fetchAllThingsWithCompletion:) atIndex:0];
[MyClass fetchSomeJSON]; // Call the method so we can capture the block
completionBlock = spy.argument;
// run the completion block
completionBlock(#[#"blah"], nil);
})
// If I remove the dispatch_async block, this test passes fine.
// If I add it in again the test fails, probably because its not waiting
it(#"should return the right count", ^{
// entityCount is a block that performs a fetch request count
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
})
// This works fine, but obviously I don't want to wait a second
it(#"should return the right count after waiting for a second", ^{
sleep(1);
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
});
};
If I remove the dispatch_async line, then I can get my test to run quickly. The only way I can get my test suite to run when using dispatch_async is to sleep(1) after calling the completion block. Using sleep() makes me think that I'm not approaching it in the right way. I have tried using shouldEventually but this doesn't seem to re-fetch my count value.
Have you tried these asynchronous block macros?
#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO
#define BlockFinished() blockFinished = YES
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished)
I have tried several approaches to solving this, none feel right.
1) Move the dispatch_async to its own class
+ (void)dispatchOnMainQueue:(Block)block
{
if ([NSThread currentThread] == [NSThread mainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
+ (void)dispatchOnBackgroundQueue:(Block)block
{
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(background, block);
}
Then during test execution, swizzle the background dispatch to occur on the main queue. This worked, but was unpredictable. It also felt so wrong!
2) Move the setup code to Kiwi's beforeAll block, then sleep the main thread. This works as the Kiwi tests are run on the main thread, so we're effectively saying "let the background operations happen before carrying on with the tests". I think this is what I'm going to use. Yes it makes my unit tests run slower, but they pass when they should do, and fail when they should
describe(#"MyAction", ^{
__block void (^completionBlock)(NSArray *array, NSError *error);
beforeAll(^{
// Stub the http client
id mockClient = [AFHTTPClient mock];
[WRNAPIClient stub:#selector(sharedClient) andReturn:mockClient];
// capture the block argument
KWCaptureSpy *spy = [mockClient captureArgument:#selector(fetchAllThingsWithCompletion:) atIndex:0];
[WRNTaskImporter importAllTasksFromAPI];
completionBlock = spy.argument;
// run the completion block
completionBlock(#[#"blah"], nil);
// Wait for background import to complete
[NSThread sleepForTimeInterval:0.1];
})
// This works
it(#"should return the right count", ^{
// entityCount is a block that performs a fetch request count
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
})
};
The caveat of this approach is that it only works when you aren't changing any data before a test. Say for example I insert 4 entities, and want to check each entity was inserted as expected. This option would work here. If I needed to re-run the import method and check that the count hadn't increased, I would need to add another [NSThread sleepForTimeInterval:0.1] after calling the insertion code.
For normal block based Kiwi tests you should probably use either the expectFutureValue shouldEventually method, or KWCaptureSpy to test your code, but this may not help when calling nested blocks.
If anyone has a more appropriate method for testing cases like these I'm happy to hear it!
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I need to send 100 network requests to my server one-by-one and get notified when the 100th is done.
I'm using AFNetworking and was thinking about a solution of this problem. Can anyone recommend me something?
A couple of thoughts:
If really just going to run each request serially (i.e. one after another), you could do:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All operations done");
}];
for (NSInteger i = 0; i < operationCount; i++) {
AFHTTPRequestOperation *operation = ... // create your operation here
[completionOperation addDependency:operation];
[queue addOperation:operation];
}
[queue addOperation:completionOperation];
Note, using operation queue like this offers the advantage that you can easily cancel all the operations in that queue should you ever need to.
If the order that these are performed is critical, you might want to establish explicit dependencies between the operations, e.g.:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All operations done");
}];
NSOperation *priorOperation = nil;
for (NSInteger i = 0; i < operationCount; i++) {
AFHTTPRequestOperation *operation = ... // create your operation here
[completionOperation addDependency:operation];
if (priorOperation) [operation addDependency:priorOperation];
[queue addOperation:operation];
priorOperation = operation;
}
[queue addOperation:completionOperation];
The question for me is whether you absolutely only want to run one at a time. You pay a significant performance penalty for that. Generally you'd use that first code sample (where the only explicit dependencies are to the completion operation) and set maxConcurrentOperationCount to something like 4, enjoying concurrency and its consequent significant performance gain (while at the same time, constraining the degree of concurrency to some reasonable number that won't use up all of your worker threads, risk having requests time out, etc.).
You haven't said what these 100 operations are, but if it's a bunch of downloads, you might want to consider a "lazy loading" pattern, loading the data asynchronously as you need it, rather than all at once.
If downloading images, for example, you might achieve this using the AFNetworking UIImageView category.
This is a specific form of a common question, which is "how do I call a sequence of block operations and get notified when the last one finishes?"
One idea is to make a "to-do list" using the parameters for each request. Say each request takes a number 0..99. Now pseudo-code would looks like this:
#property(nonatomic, copy) void (^done)(BOOL); // we'll need to save a completion block
#property(nonatomic, strong) NSMutableArray *todo; // might as well save this too
- (void)makeRequestsThenInvoke:(void (^)(BOOL))done {
self.todo = [NSMutableArray arrayWithArray:#[#99, #98, #97 ... #0]];
// make this in a loop using real params to your network request (whatever distinguishes each request)
self.done = done;
[self makeRequests];
}
- (void)makeRequests {
if (!self.todo.count) { // nothing todo? then we're done
self.done(YES);
self.done = nil; // avoid caller-side retain cycle
return;
}
// otherwise, get the next item todo
NSNumber *param = [self.todo lastObject];
// build a url with param, e.g. http://myservice.com/request?param=%# <- param goes there
[afManager post:url success:success:^(AFHTTPRequestOperation *operation, id responseObject) {
// handle the result
// now update the todo list
[self.todo removeLastObject];
// call ourself to do more, but use performSelector so we don't wind up the stack
[self performSelector:#selector(makeRequests) withObject:nil afterDelay:0.0];
}];
}
I am developing an app and when it starts its execution it has to get some data from the webService, categories, Image of loading(it changes sometimes), info "how to use" ( also can change in the server, client specifications..). To get this data I call some methods like this one (I have four similar methods, one for each thing I need) :
-(void) loadAppInfo
{
__weak typeof(self) weakSelf = self;
completionBlock = ^(BOOL error, NSError* aError) {
if (error) {
// Lo que sea si falla..
}
[weakSelf.view hideToastActivity];
};
[self.view makeToastActivity];
[wpNetManager getApplicationInfoWithCompletionBlock:completionBlock];
}
In my Network manager I have methods like this one :
- (void)getApplicationInfoWithCompletionBlock:(CompletionBlock)completionBlock
{
NSString * lang = #"es";//[[NSLocale preferredLanguages] objectAtIndex:0];
NSString *urlWithString = [kAPIInfoScreens stringByAppendingString:lang];
NSMutableURLRequest *request = nil;
request = [self requestWithMethod:#"GET" path:urlWithString parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Print the response body in text
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
NSDictionary *informations = [json objectForKey:kTagInfoSplash];
if([json count]!= 0){
for (NSDictionary *infoDic in informations) {
Info *info = [Info getInfoByTitle:[infoDic objectForKey:kTagInfoTitle]];
if (info) {
// [User updateUserWithDictionary:dic];
} else {
[Info insertInfoWithDictionary:infoDic];
}
}
[wpCoreDataManager saveContext];
}
if (completionBlock) {
completionBlock(NO, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error Registro: %#", error);
if (completionBlock) {
completionBlock(YES, error);
}
}];
[self enqueueHTTPRequestOperation:operation];
}
So what I do is call this methods in the viewDidLoad:
[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashFromWS];
So, instead of call this methods one by one. I think I can use GCD to manage this while a load image is called until everything is done and then call the next ViewController. It is a good solution what I believe? if it is the problem is that I do not know how to add some blocks to a gcd.
I am trying to do this instead of calling he last four methods in ViewDidLoad. But it works weird:
-(void)myBackGroundTask
{
[self.view makeToastActivity];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashDataFromWS ];
dispatch_async(dispatch_get_main_queue(), ^{
[self.view hideToastActivity];
[self nextController];
});
});
}
[self nextController] method is called before I had everything save in Core Data and I have errors..
Since all your four methods
[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashFromWS];
are asynchronous, it should be clear why the statement
[self nextController];
is executed before those four methods finish. Right?
Thus, there are completion handlers which get invoked when the asynchronous method finished. Too bad, none of your asynchronous methods have completion handlers. ;)
The key to approach the problem seems to have completion handlers for your asynchronous methods:
typedef void (^completion_t)(id result, NSError* error);
- (void) loadAppInfo:(completion_t)completionHandler;
- (void) loadCountriesFromJson:(completion_t)completionHandler;
- (void) loadCategoriesFromWS:(completion_t)completionHandler;
- (void) loadSplashFromWS:(completion_t)completionHandler;
It seems, you want to start ALL four asynchronous methods concurrently.
How and when you have to invoke the statement [self nextController] depends on whether there are any dependencies for this call to the eventual result of the above four asynchronous methods.
For example, you may state:
A. [self nextController] shall be executed when loadAppInfo: finishes successfully. All other asynchronous methods are irrelevant.
The solution looks like this:
[self loadAppInfo:^(id result, NSError*error){
if (error == nil) {
[self nextController];
}
}];
[self loadCountriesFromJson:nil];
[self loadCategoriesFromWS:nil];
[self loadSplashFromWS:nil];
If the above statement depends only on one of those methods, the solution is quite obvious and simple. It will get immediately more complex when you have a requirement like this:
B. [self nextController] shall be executed when ALL four asynchronous methods finished successfully (or more than one, and all other methods are irrelevant).
There are a few approaches how one can solve that. One would be to use a dispatch group, or a semaphore and a few state variables and dispatch queues to ensure concurrency. However, this is quite elaborate, would ultimately cause to block a thread, cannot be cancelled, and is also quite suboptimal (besides that it also looks hackish). Thus, I will not discuss that solution.
Using NSOperation and Dependencies
Another approach is to utilize NSOperation's dependencies. This requires to wrap each asynchronous method into a NSOperation subclass. Your methods are already asynchronous, this means that you need to take this into account when designing your subclasses.
Since one can only establish a dependency from one to another NSOperation, you also need to create a NSOperation subclass for your statement
[self nextController]
which needs to be wrapped into its own NSOperation subclass.
Well assuming you correctly subclassed NSOperation, at the end of the day, you get five modules and five header files:
LoadAppInfoOperation.h, LoadAppInfoOperation.m,
LoadCountriesFromJsonOperation.h, LoadCountriesFromJsonOperation.m,
LoadCategoriesFromWSOperation.h, LoadCategoriesFromWSOperation.m,
LoadSplashFromWSOperation.h, LoadSplashFromWSOperation.m
NextControllerOperation.h, NextControllerOperation.m
B. NextControllerOperation shall be started when ALL four Operations finished successfully:
In code this looks as follows:
LoadAppInfoOperation* op1 = ...;
LoadCountriesFromJsonOperation* op2 = ...;
LoadCategoriesFromWSOperation* op3 = ...;
LoadSplashFromWSOperation* op4 = ...;
NextControllerOperation* controllerOp = ...;
[controllerOp addDependency:op1];
[controllerOp addDependency:op2];
[controllerOp addDependency:op3];
[controllerOp addDependency:op4];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation: op1];
[queue addOperation: op2];
[queue addOperation: op3];
[queue addOperation: op4];
[queue addOperation: controllerOp];
Looks nice? No?
A more appealing approach: Promises
If this solution with NSOperations doesn't look nice, is too elaborated (five NSOperation subclasses!) or whatever, here is a more appealing approach which uses a third party library which implements Promises.
Before I explain how Promises work and what they are for (see wiki for a more general description), I would like to show the final code right now here, and explain how to get there later.
Disclosure: The example code here utilizes a third party library RXPromise which implements a Promise according the Promise/A+ specification. I'm the author of the RXPromise library.
There are a few more Promise libraries implemented in Objective-C, but you may take a look into RXPromise anyway ;) (see below for a link)
The key is to create asynchronous methods which return a promise. Assuming ALL your methods are now asynchronous and have a signature like below:
- (RXPromise*) doSomethingAsync;
Then, your final code will look as follows:
// Create an array of promises, representing the eventual result of each task:
NSArray* allTasks = #[
[self loadAppInfo],
[self loadCountriesFromJson],
[self loadCategoriesFromWS],
[self loadSplashFromWS]
];
...
This above statement is a quite a short form of starting a number of tasks and holding their result objects (a promise) in an array. In other words, the array allTasks contains promises whose task has been started and which now run all concurrently.
Now, we continue and define what shall happen when all tasks within this array finished successfully, or when any tasks fails. Here we use the helper class method all::
...
[RXPromise all: allTasks]
.then(^id(id results){
// Success handler block
// Parameter results is an array of the eventual result
// of each task - in the same order
... // do something with the results
return nil;
},^id(NSError*error){
// Error handler block
// error is the error of the failed task
NSLog(#"Error: %#, error");
return nil;
});
See the comments in the code above to get an idea how the success and the error handler - which get called when all tasks have been finished - is defined with the "obscure" then.
The explanation follows:
Explanation:
The code below uses the RXPromise library. You can obtain the source code of RXPromise Library which is available at GitHub.
There are a few other implementations (SHXPromise, OMPromises and more) and with a little effort it should be possible to port the code below to other promise libraries as well.
First, you need a variant of your asynchronous methods which looks as follows:
- (RXPromise*) loadAppInfo;
- (RXPromise*) loadCountriesFromJson;
- (RXPromise*) loadCategoriesFromWS;
- (RXPromise*) loadSplashFromWS;
Here, note that the asynchronous methods don't have a completion handler. We don't need this since the returned object -- a Promise -- represents the eventual result of the asynchronous task. This result may also be an error when the task fails.
I've refactored your original methods in order to better utilize the power of promises:
An asynchronous task will create the promise, and it must eventually "resolve" it either with the eventual result via fulfillWithValue:, or when it fails, with an error via rejectWithReason:. See below how a RXPromise is created, immediately returned from the asynchronous method, and "resolved" later when the task finished or failed.
Here, your method getApplicationInfo returns a promise whose eventual value will be the HTTP response data, that is a NSData containing JSON (or possibly an error):
- (RXPromise*)getApplicationInfo
{
RXPromise* promise = [[RXPromise alloc] init];
NSString * lang = #"es";//[[NSLocale preferredLanguages] objectAtIndex:0];
NSString *urlWithString = [kAPIInfoScreens stringByAppendingString:lang];
NSMutableURLRequest *request = nil;
request = [self requestWithMethod:#"GET" path:urlWithString parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[promise fulfillWithValue:responseObject]
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[promise rejectWithReason:error];
}];
[self enqueueHTTPRequestOperation:operation];
return promise;
}
A few further notes about promises:
A client can obtain the eventual result respectively the error through registering handler blocks through using the property then:
promise.then(<success_handler>, <error_handler>);
Handlers or optional, but you usually set either one or both which handle the result.
Note: With RXPromise you can register handler blocks when and where you want, and as many as you want! RXPromise is fully thread safe. You just need to keep a strong reference to the promise somewhere or as long as needed. You don't need to keep a reference, even when you setup handlers, though.
The handler block will be executed on a private queue. This means, you don't know the execution context aka thread where the handler will be executed, except you use this variant:
promise.thenOn(dispatch_queue, <success_handler>, <error_handler>);
Here, dispatch_queue specifies the queue where the handler (either the success OR the error handler) will be executed.
Two or more asynchronous tasks can be executed subsequently (aka chained), where each task produces a result which becomes the input of the subsequent task.
A short form of "chaining" of two async methods looks like this:
RXPromise* finalResult = [self asyncA]
.then(^id(id result){
return [self asyncBWithResult:result]
}, nil);
Here, asyncBWithResult: will be executed only until after asyncA has been finished successfully. The above expression returns a Promise finalResult which represents the final result of what asyncBWithResult: "returns" as its result when it finishes, or it contains an error from any task that fails in the chain.
Back to your problem:
Your method loadAppInfo now invokes asynchronous method getApplicationInfo in order to obtain the JSON data. When that succeeded, it parsers it, creates managed objects from it and saves the managed object context.
It returns a promise whose value is the managed object context where the objects have been saved:
- (RXPromise*) loadAppInfo {
RXPromise* promise = [[RXPromise alloc] init];
[self getApplicationInfo]
.then(^(id responseObject){
NSError* err;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&err];
if (json == nil) {
return err;
}
else {
[wpCoreDataManager.managedObjectContext performBlock:^{
NSDictionary *informations = [json objectForKey:kTagInfoSplash];
if([json count]!= 0){
for (NSDictionary *infoDic in informations) {
Info *info = [Info getInfoByTitle:[infoDic objectForKey:kTagInfoTitle]];
if (info) {
// [User updateUserWithDictionary:dic];
} else {
[Info insertInfoWithDictionary:infoDic];
}
}
[wpCoreDataManager saveContext]; // check error here!
[promise fulfillWithValue:wpCoreDataManager.managedObjectContext];
}
else {
[promise fulfillWithValue:nil]; // nothing saved
}
}];
}
}, nil);
return promise;
}
Notice how performBlock has been utilized to ensure the managed objects are properly associated to the execution context of its managed object context. Additionally, the asynchronous version is used, which fits nicely into the solution utilizing promises.
Having refactored these two methods, which merely perform what you intend to accomplish, and also having refactored the other asynchronous methods which now return a promise like the refactored above methods, you can now finish your task as shown at the start.
GCD to manage this while a load image is called until everything is done and then call the next ViewController. It is a good solution what I believe?
The general rule of thumb is to operate on the highest level of abstraction available.
In this case it means using NSOperation subclasses. You can create a private queue, and schedule you operations in such a way that turning off the loading image will happen only after all operations are complete, e.g. by
NSOperation *goForward = [MyGoForwardOperation new]; // you define this subclass
NSOperation *loadSomething = [MyLoadSomethingOperation new];
NSOperation *loadAnother = [MyLoadAnotherThingOperation new];
[goForward addDependency: loadOperation];
[goForward addDependency: loadAnother];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation: loadSomething];
[queue addOperation: loadAnother];
[[NSOperationQueue mainQueue] addOperation: goForward];
Note that in this example goForward will run on main thread, but after background operations finish.
You'll need to carefully program your MyLoadSomethingOperation for this to work, read up on subclassing NSOperation or subclass AFHTTPRequestOperation since you're using it anyway.
[self nextController] method is called before I had everything
Yes, you should search on saving to Core Data on background thread; this is a big topic in itself.
I'm working on an application that create contents and send it to an existing backend. Content is a title, a picture and location. Nothing fancy.
The backend is a bit complicated so here is what I have to do :
Let the user take a picture, enter a title and authorize the map to use its location
Generate a unique identifier for the post
Create the post on the backend
Upload the picture
Refresh the UI
I've used a couple of NSOperation subclasses to make this work but I'm not proud of my code, here is a sample.
NSOperation *process = [NSBlockOperation blockOperationWithBlock:^{
// Process image before upload
}];
NSOperation *filename = [[NSInvocationOperation alloc] initWithTarget: self selector: #selector(generateFilename) object: nil];
NSOperation *generateEntry = [[NSInvocationOperation alloc] initWithTarget: self selector: #selector(createEntry) object: nil];
NSOperation *uploadImage = [[NSInvocationOperation alloc] initWithTarget: self selector: #selector(uploadImageToCreatedEntry) object: nil];
NSOperation *refresh = [NSBlockOperation blockOperationWithBlock:^{
// Update UI
[SVProgressHUD showSuccessWithStatus: NSLocalizedString(#"Success!", #"Success HUD message")];
}];
[refresh addDependency: uploadImage];
[uploadImage addDependency: generateEntry];
[generateEntry addDependency: filename];
[generateEntry addDependency: process];
[[NSOperationQueue mainQueue] addOperation: refresh];
[_queue addOperations: #[uploadImage, generateEntry, filename, process] waitUntilFinished: NO];
Here are the things I don't like :
in my createEntry: for example, I'm storing the generated filename in a property, which mees with the global scope of my class
in the uploadImageToCreatedEntry: method, I'm using dispatch_async + dispatch_get_main_queue() to update the message in my HUD
etc.
How would you manage such workflow ? I'd like to avoid embedding multiple completion blocks and I feel like NSOperation really is the way to go but I also feel there is a better implementation somewhere.
Thanks!
You can use ReactiveCocoa to
accomplish this pretty easily. One of its big goals is to make this kind of
composition trivial.
If you haven't heard of ReactiveCocoa before, or are unfamiliar with it, check
out the Introduction
for a quick explanation.
I'll avoid duplicating an entire framework overview here, but suffice it to say that
RAC actually offers a superset of promises/futures. It allows you to compose and
transform events of completely different origins (UI, network, database, KVO,
notifications, etc.), which is incredibly powerful.
To get started RACifying this code, the first and easiest thing we can do is put
these separate operations into methods, and ensure that each one returns
a RACSignal. This isn't strictly necessary (they could all be defined within
one scope), but it makes the code more modular and readable.
For example, let's create a couple signals corresponding to process and
generateFilename:
- (RACSignal *)processImage:(UIImage *)image {
return [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
// Process image before upload
UIImage *processedImage = …;
[subscriber sendNext:processedImage];
[subscriber sendCompleted];
}];
}
- (RACSignal *)generateFilename {
return [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
NSString *filename = [self generateFilename];
[subscriber sendNext:filename];
[subscriber sendCompleted];
}];
}
The other operations (createEntry and uploadImageToCreatedEntry) would be very similar.
Once we have these in place, it's very easy to compose them and express their
dependencies (though the comments make it look a bit dense):
[[[[[[self
generateFilename]
flattenMap:^(NSString *filename) {
// Returns a signal representing the entry creation.
// We assume that this will eventually send an `Entry` object.
return [self createEntryWithFilename:filename];
}]
// Combine the value with that returned by `-processImage:`.
zipWith:[self processImage:startingImage]]
flattenMap:^(RACTuple *entryAndImage) {
// Here, we unpack the zipped values then return a single object,
// which is just a signal representing the upload.
return [self uploadImage:entryAndImage[1] toCreatedEntry:entryAndImage[0]];
}]
// Make sure that the next code runs on the main thread.
deliverOn:RACScheduler.mainThreadScheduler]
subscribeError:^(NSError *error) {
// Any errors will trickle down into this block, where we can
// display them.
[self presentError:error];
} completed:^{
// Update UI
[SVProgressHUD showSuccessWithStatus: NSLocalizedString(#"Success!", #"Success HUD message")];
}];
Note that I renamed some of your methods so that they can accept inputs from
their dependencies, giving us a more natural way to feed values from one
operation to the next.
There are huge advantages here:
You can read it top-down, so it's very easy to understand the order that
things happen in, and where the dependencies lie.
It's extremely easy to move work between different threads, as evidenced by
the use of -deliverOn:.
Any errors sent by any of those methods will automatically cancel all the
rest of the work, and eventually reach the subscribeError: block for easy
handling.
You can also compose this with other streams of events (i.e., not just
operations). For example, you could set this up to trigger only when a UI
signal (like a button click) fires.
ReactiveCocoa is a huge framework, and it's unfortunately hard to distill the
advantages down into a small code sample. I'd highly recommend checking out the
examples for when to use
ReactiveCocoa
to learn more about how it can help.
A couple of thoughts:
I would be inclined to avail myself of completion blocks because you probably only want to initiate the next operation if the previous one succeeded. You want to make sure that you properly handle errors and can easily break out of your chain of operations if one fails.
If I wanted to pass data from operation to another and didn't want to use some property of the caller's class, I would probably define my own completion block as a property of my custom operation that had a parameter which included the field that I wanted to pass from one operation to another. This assumes, though, that you're doing NSOperation subclassing.
For example, I might have a FilenameOperation.h that defines an interface for my operation subclass:
#import <Foundation/Foundation.h>
typedef void (^FilenameOperationSuccessFailureBlock)(NSString *filename, NSError *error);
#interface FilenameOperation : NSOperation
#property (nonatomic, copy) FilenameOperationSuccessFailureBlock successFailureBlock;
#end
and if it wasn't a concurrent operation, the implementation might look like:
#import "FilenameOperation.h"
#implementation FilenameOperation
- (void)main
{
if (self.isCancelled)
return;
NSString *filename = ...;
BOOL failure = ...
if (failure)
{
NSError *error = [NSError errorWithDomain:... code:... userInfo:...];
if (self.successFailureBlock)
self.successFailureBlock(nil, error);
}
else
{
if (self.successFailureBlock)
self.successFailureBlock(filename, nil);
}
}
#end
Clearly, if you have a concurrent operation, you'll implement all of the standard isConcurrent, isFinished and isExecuting logic, but the idea is the same. As an aside, sometimes people will dispatch those success or failures back to the main queue, so you can do that if you want, too.
Regardless, this illustrates the idea of a custom property with my own completion block that passes the appropriate data. You can repeat this process for each of the relevant types of operations, you can then chain them all together, with something like:
FilenameOperation *filenameOperation = [[FilenameOperation alloc] init];
GenerateOperation *generateOperation = [[GenerateOperation alloc] init];
UploadOperation *uploadOperation = [[UploadOperation alloc] init];
filenameOperation.successFailureBlock = ^(NSString *filename, NSError *error) {
if (error)
{
// handle error
NSLog(#"%s: error: %#", __FUNCTION__, error);
}
else
{
generateOperation.filename = filename;
[queue addOperation:generateOperation];
}
};
generateOperation.successFailureBlock = ^(NSString *filename, NSData *data, NSError *error) {
if (error)
{
// handle error
NSLog(#"%s: error: %#", __FUNCTION__, error);
}
else
{
uploadOperation.filename = filename;
uploadOperation.data = data;
[queue addOperation:uploadOperation];
}
};
uploadOperation.successFailureBlock = ^(NSString *result, NSError *error) {
if (error)
{
// handle error
NSLog(#"%s: error: %#", __FUNCTION__, error);
}
else
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update UI here
NSLog(#"%#", result);
}];
}
};
[queue addOperation:filenameOperation];
Another approach in more complicated scenarios is to have your NSOperation subclass employ a technique analogous to how the standard addDependency method works, in which NSOperation sets the isReady state based upon KVO on isFinished on the other operation. This not only allows you to not only establish more complicated dependencies between operations, but also to pass database between them. This is probably beyond the scope of this question (and I'm already suffering from tl:dr), but let me know if you need more here.
I wouldn't be too concerned that uploadImageToCreatedEntry is dispatching back to the main thread. In complicated designs, you might have all sorts of different queues dedicated for particular types of operations, and the fact that UI updates are added to the main queue is perfectly consistent with this mode. But instead of dispatch_async, I might be inclined to use the NSOperationQueue equivalent:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do my UI update here
}];
I wonder if you need all of these operations. For example, I have a hard time imagining that filename is sufficiently complicated to justify its own operation (but if you're getting the filename from some remote source, then a separate operation makes perfect sense). I'll assume that you're doing something sufficiently complicated that justifies it, but the names of those operations make me wonder, though.
If you want, you might want to take a look at couchdeveloper's RXPromise class which uses promises to (a) control the logical relationship between separate operations; and (b) simplify the passing of data from one to the next. Mike Ash has a old MAFuture class which does the same thing.
I'm not sure either of those are mature enough that I'd contemplate using them in my own code, but it's an interesting idea.
I'm probably totally, biased - but for a particular reason - I like #Rob's approach #6 ;)
Assuming you created appropriate wrappers for your asynchronous methods and operations which return a Promise instead of signaling the completion with a completion block, the solution looks like this:
RXPromise* finalResult = [RXPromise all:#[[self filename], [self process]]]
.then(^id(id filenameAndProcessResult){
return [self generateEntry];
}, nil)
.then(^id(id generateEntryResult){
return [self uploadImage];
}, nil)
.thenOn(dispatch_get_main_queue() , ^id(id uploadImageResult){
[self refreshWithResult:uploadImageResult];
return nil;
}, nil)
.then(nil, ^id(NSError*error){
// Something went wrong in any of the operations. Log the error:
NSLog(#"Error: %#", error);
});
And, if you want to cancel the whole asynchronous sequence at any tine, anywhere and no matter how far it has been proceeded:
[finalResult.root cancel];
(A small note: property root is not yet available in the current version of RXPromise, but its basically very simple to implement).
If you still want to use NSOperation, you can rely on ProcedureKit and use the injection properties of the Procedure class.
For each operation, specify which type it produces and inject it to the next dependent operation. You can also at the end wrap the whole process inside a GroupProcedure class.