Say you have a method that returns information in two separate blocks, like so:
#interface SomeObject : NSObject
- (instancetype)initWithA:(NSString *)aInfo bInfo:(NSString *)bInfo;
#end
- (void)someMethod:(void (^)(NSString *aInfo))firstBlock
secondBlock:(void (^)(NSString *bInfo))secondBlock {
firstBlock(#"a"); secondBlock(#"b");
}
- (void)ourMethod:(void (^)(SomeObject *object))completionBlock {
SomeObject *someObject = [[SomeObject alloc] initWithA:aInfo bInfo:bInfo];
[self someMethod:^(NSString *aInfo) {
//
} secondBlock:^(NSString *bInfo) {
//
}];
completionBlock(someObject);
}
How would you initialize someObject and pass it back when both of the blocks have completed?
Assume that both blocks are executed asynchronously.
I tried fiddling with GCD's dispatch groups to solve this, however, it didn't seem optimal.
Since you need to create your someObject with the values obtained from the two blocks used in the call to someMethod, you need to create someObject after both blocks have been called.
- (void)ourMethod:(void (^)(BOOL initializationComplete))completionBlock {
__block NSString *a = nil;
__block NSString *b = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
[self someMethod:^(NSString *aInfo) {
a = aInfo;
dispatch_group_leave(group);
} secondBlock:^(NSString *bInfo) {
b = bInfo;
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
SomeObject *someObject = [[SomeObject alloc] initWithA:a bInfo:b];
completionBlock(someObject);
});
}
This doesn't block the caller of ourMethod and it ensures the completion block is only called once both blocks are done.
This solution assumes the two blocks are run asynchronously.
You can use a semaphore, but-- in general-- making an asynchronous operation synchronous is a red flag indicating bad design.
Are the two blocks asynchronous in and of themselves? If so, you could have __block BOOL firstDone = NO; and __block BOOL secondDone = NO; and check appropriately to see if it is time to call the completionBlock. Still ugly and you'll want a synchronization primitive in there to ensure you don't hit a race, but that'd work.
If firstBlock() and secondBlock() are synchronous and on the same queue, then just call completionBlock() after the second is done.
Or, alternatively, if they are asynchronous and simultaneously scheduled, toss 'em on an asynchronous queue and then toss a barrier block on the queue that calls the completionBlock.
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.
In my iOS application I am using Core Data.
For table View listing I use NSFetchedResultsController and
Connecting to Remote store I use NSIncrementalStore.
My FetchedResultsController Context is having MainQueue Cuncurrency type.(I couldn't do it with a PrivateQueueCurrencyTYpe).
For resolving Fault, for a many relationship, the executeFetchResultsCall:withContext:error method is executed from my IncrementalStore subclass.
Inside the executeFetchResults method, I will invoke the API (connecting to remote server) if it is not available in my local database.
myarray = [object representationsForRelationship:#"manyconnection" withParams:nil];
Now I need the results array in return synchronously to be returned to the ExecuteFetchResultsMethod. Also this operation should be executed on Main thread.
So I am having only one option to fetch the results from server which causes the UI to unresponsive for the specified sleep time.
-(RequestResult*)makeSyncJsonRequest{
__block RequestResult *retResult = [[RequestResult alloc] init];
__block BOOL block = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
void (^resultBlock)(RequestResult*) = ^(RequestResult* result){
if(!retResult.error)
retResult = result;
block = NO;
dispatch_group_leave(group);
};
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
dispatch_group_enter(group);
[self makeAsyncJsonRequestWithBlock:resultBlock];
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return retResult;
}
As the above operation is being executed on main thread,the UI hangs.
Inorder to make the UI smoother, I need to carry out the executeFetchrequest in some other thread which is not possible.
It also expects the results array in return.
Is there any option to carry out this something like in a completion handler manner?
or
Any alternate methods or design to work this proper.
Any Help is highly appreciated.
This is a skeleton, using a dispatch_group, assuming you are using an NSFetchedResultsController to update your UITableView:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do your setup (FetchedResultsController and such)
[self syncData];
}
- (void)syncData
{
NSArray<Entity*> *results = [self fetchData];
BOOL needsUpdateFromServer = YES; // check your results and set this bool accordingly
if (!needsUpdateFromServer) {
return;
}
__block ServerResponse *fromServer = nil;
__block dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self loadDataFromServer:^(ServerResponse *response) {
fromServer = response;
dispatch_group_leave(group);
}];
dispatch_group_notify(group,dispatch_get_main_queue(),^{
[self persistData:fromServer];
/*
According to our discussion, you are using an NSFetchedResultsController.
So your tableView should update automatically after persisting the data.
*/
});
}
- (void)loadDataFromServer:(void (^)(ServerResponse *response))completion
{
// [someDownloadService downloadDataFromServerInBackgroundWithCompletion:^(ServerResponse* response){
dispatch_async(dispatch_get_main_queue(), ^{
completion(response);
});
// }];
}
- (NSArray<Entity*>*)fetchData
{
NSArray<Entity*> *results = nil;
// fetch from core data and return it
return results;
}
- (void)persistData:(NSArray<ServerResponse*> *)serverResponses
{
// parse whatever you get from server
// ... and persist it using Core Data
}
#end
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 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!
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.