iOS/AFNetworking 3.0: Complete multiple requests in order - ios

I've the following code below (example code) that sends an API GET request multiple times.
- (void)listOfPeople:(NSArray *)array {
for (int i = 0; i < array.count; i++) {
Person *person = [array objectAtIndex:i];
[personClient getPersonData:person.fullName onSuccess:^(id result) {
// change data here
} onFailure:^(NSError *error) {
}];
}
}
The code doesn't work very well because the API requests finishes in a different order every time. I need to complete each api request in order. I believe I need to wait until either the completion block or the failure block is finished before continuing the for loop. Can someone point me in the right direction unless there is a better way to accomplish this task. I've tried dispatch group, but it didn't complete each request in order.

Get rid of the for loop, and instead make a recursive function that calls itself from the completion handler to get the next Person. That way when each call completes, it will make the call to get the next one.
Something like this:
- (void)getPersonFromArray:(NSArray *)array atIdx:(NSInteger)idx {
if (idx < array.count)
{
Person *person = [array objectAtIndex:idx];
[personClient getPersonData:person.fullName onSuccess:^(id result)
{
// Do something useful with Person here...
// ...
[self getPersonFromArray:array atIdx(idx + 1)];
} onFailure:^(NSError *error) {
// Handle errors here
// ...
}];
}
}

Related

Concurrency iterating over NSArray

I've an array of user objects. I need to iterate over this array, and then, for each user, I need to make a request to fetch data, based on its user id. This request is an async request (and for reasons I can't make it sync). Here is the edited code using dispatch_group calls:
EDITED
for (User *user in self.userList) {
dispatch_group_enter(self.usersGroup);
[self.wrapper getUserDataWithId:user.userid completion:^(Owner *owner, NSError *error) {
user.owner = owner;
dispatch_group_leave(self.usersGroup);
}];
}
dispatch_group_notify(self.usersGroup, dispatch_get_main_queue(), ^{
// All requests have finished
// Stuff with updated userlist
});
The main issue is obvious: when first requests have finished, the user reference it has is not the one that has launched the request, so I don't have the correct association.
Is there any easy and elegant way to solve this issue? Thanks in advance!
As #Paulw11 suggested the loop will not wait for you to execute the block so i'm suggesting you to use recursive function like this instead of for loop.
- (void)getOwnerForObectAtIndex:(NSInteger)index {
if (index < self.userList) {
User *user = [self.userList objectAtIndex:index];
[self.wrapper getUserDataWithId:user.userid completion:^(Owner *owner, NSError *error) {
user.owner = owner;
[self getOwnerForObectAtIndex:index+1];
}];
}
else {
//Now all User have its corresponding owner, Handle array Now.
}
}
Now call this function like this [self getOwnerForObectAtIndex:0];

iOS - How to increase variable in block using cycle

I need to create an entity on the server and then upload few images to the server.
So first block display success creating entity on the server then I starting upload 10 images one by one in cycle, but the app send the notification not after last 10 image was uploaded, so 'i' variable can be 10 even not be a 10 in the order. I am not sure but seems iteration in the block is not right. So I just want to be sure that the 10 images was uploaded and just then invoke sending notification.
So I skip some blocks parameters and failure options, array that I use for getting images to upload and etc. Just think about my blocks as an example that display success invocation after '{'.
// success first block
block1
{
// cycle from 0 to 10
for (NSInteger i = 0; i <=10; i++)
{
// success upload image to the server block
block2
{
// if I did 10 uploads I need to send notification.
if (i == 10)
{
// send notification here when last block returned success....
}
}
}
}
If you're creating a bunch of AFHTTPRequestOperation objects, then the most logical approach would be to create a completion operation and make it dependent upon the request operations:
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// code to be run when the operations are all done should go here
}];
for (...) {
// create `operation` AFHTTPRequestOperation however you want and add it to some queue
// but just make sure to designate the completion operation dependency
[completionOperation addDependency:operation];
}
// now that all the other operations have been queued, you can now add the completion operation to whatever queue you want
[[NSOperationQueue mainQueue] addOperation:completionOperation];
You can use Dispatch Group as the following.
// success first block
block1
{
dispatch_group_t group = dispatch_group_create();
// cycle from 0 to 10
__block NSUInteger successCount = 0;
for (NSInteger i = 0; i <=10; i++)
{
dispatch_group_enter(group);
// upload image to the server block
successOrErrorBlock
{
if (success)
successCount++;
dispatch_group_leave(group);
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (successCount == 10) {
// send notification here when last block returned success....
}
});
}
The pattern I follow when invoking more than a couple blocks asynchronously is to create a to-do list (array) and perform that list recursively, like this:
// say this is our asynch operation. presume that someParameter fully
// describes the operation, like a file to be uploaded
- (void)performSomeAsynchOperationDefinedBy:(id)someParameter completion:(void (^)(BOOL, NSError *))completion {
// this could wrap any operation, like anything from AFNetworking
}
- (void)doOperationsWithParameters:(NSArray *)parameters completion:(void (^)(BOOL, NSError *))completion {
if (!parameters.count) return completion(YES, nil);
id nextParameter = someParameters[0];
NSArray *remainingParameters = [parameters subarrayWithRange:NSMakeRange(1, parameters.count-1)];
[self performSomeAsynchOperationDefinedBy:nextParameter completion:^(BOOL success, NSError *error)) {
if (!error) {
[self doManyOperations:remainingParameters completion:completion];
} else {
completion(NO, error);
}
}];
}
Now, to do several operations, place the parameters in an array, like:
NSArray *parameters = #[#"filename0", #"filename1", ...];
[self doOperationsWithParameters:parameters completion:(BOOL success, NSError *error) {
NSLog(#"ta-da!");
}];
A variant on the above with a progress block invoked up front and after each recursion (with a progress percentage initialCount/remainingCount) should be straight forward from the example provided.
Another variation would be to wrap both the param array and the completion block in a single NSObject, so the recursive call could be made with performSelector:, the benefit of doing this would be to not wind up the stack on a long recursion.

iOS: Asynchronous method with block callback in a while loop

I have the following requirement:
Given a hierarchical tree-like structure, I am performing a breadth-first-search to walk through the WHOLE dataset. The data is being provided by an API with a method : (makes a request to a server using AFNetworking, saves the result to Core Data and calls back the completion block on success with the stored entries)
-(void) getChildrenForNodeId:(NSNumber*)nodeId
completion:(void (^)(NSArray *nodes))completionBlock;
The method which a controller executes to fetch data:
-(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock{
NSNumber *rootId = ...
[MyNetworkManager getChildrenForNodeId:rootId completion:^(NSArray *nodes){
for(Node *node in nodes){
[self iterateOverNode:node.nodeId];
}
//execute completionBlock with nodes fetched from database that contain all their children until the very last leaf
}];
}
Here is the problem:
-(void)iterateOverNode:(NSNumber*)nodeId {
NSMutableArray *elements = [NSMutableArray array];
[elements addObject:nodeId];
while ([elements count]) {
NSNumber *current = [elements objectAtIndex:0];
[MyNetworkManager getChildrenForNodeWithId:current completion:^(NSArray *nodes) {
/**
In order to continue with the loop the elements array must be updated. This can only happen once we have retrieved the children of the current node.
However since this is in a loop, all of these requests will be sent off at the same time, thus unable to properly keep looping.
*/
for(Node *node in nodes){
[elements addObject:node.nodeId];
}
[elements removeObjectAtIndex:0];
}];
}
}
Basically I need the result of the callback to control the flow of the while loop but I am not sure how to achieve it. My understanding is that the request to getChildrenForNodeWithId:completion: from within the while-loop should happen in a new thread in a SERIAL order so that another should commence after the first one has completed. I am not sure how to achieve this neither with NSOperation nor with GCD. Any help will be greatly appreciated.
What you need here is some recursion. This problem is tricky as we also need a way to track and detect the point at which we have explored every branch to a leaf node.
I'm not a expert with tree search algorithms, so some folks could probably improve on my answer here. Kick this off by calling it with the root node id. self.trackingArray is an NSMutableArray property with __block qualifier. Each time we start a request for a Node, we add it's nodeId into this array, and when it returns, we remove it's nodeId, and add the nodeIds of it's children. We can then know that when count of the tracking array reaches 0, every request made has returned, and has not added child ids to the array. Once you detect we are finished, you could call a saved block or a delegate method.
This solution does not include any error handling. If any request fails, it won't be retried, and all child nodes will be ignored.
- (void)getNodesRecursively:(NSNumber *)nodeId {
// Only do this once
if (self.trackingArray == nil) {
self.trackingArray = [NSMutableArray new];
[self.trackingArray addObject:nodeId];
}
__weak typeof(self) weakSelf = self;
[MyNetworkManager getChildrenForNodeWithId:nodeId completion:^(NSArray *nodes) {
[self.trackingArray removeObject:nodeId];
for (Node *node in nodes) {
[weakSelf.trackingArray addObject:node.nodeId];
[weakSelf getNodesRecursively:node.nodeId];
}
if (weakSelf.trackingArray.count == 0) {
// We are done.
// Reset the state of the trackingArray
self.trackingArray = nil;
// call a method here to notify who ever needs to know.
[weakSelf allNodesComplete];
}
}];
}
Your other methods would look something like this
-(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock{
// Save the block to a property
self.completion = completionBlock;
// Call -getNodesRecursively with root id
[self getNodesRecursively:rootNodeId];
}
Then you could have a second method
- (void)allNodesComplete {
// Call saved block
// Completion should load nodes from core data as needed
self.completion();
}
I haven't tested the code, but does that approach seem sensible? I'm assuming we don't need to capture the here nodes, as they can be loaded from core data as required.

Completion blocks in for loop

Currently I am trying to do some async and concurrent tasks, and I am using Azures blob to upload all the images, however the concern is that, for every blob I need to get a SASURL and then upload the images. Also the another side towards it is that I want to have all the operations of the images completed to be uploaded, and hence send a final upload to the database. Although I can send the operation to the database earlier, without having the confirmation of the images completed, but I just wanted to make sure, that the operation does gets completed.
Below is the code for the SASURL block.
- (void)storageServiceBlob:(NSArray*)images
{
StorageService *storageService = [StorageService getInstance];
NSLog(#"%#",[storageService containers]);
NSLog(#"%#",[storageService blobs]);
for (int i = 0; i < [images count]; i++) {
NSString *file_name = [images objectAtIndex:i];
NSString *result = [self imageName:file_name];
NSLog(#"Final: %#", result);
[storageService getSasUrlForNewBlob:result forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
NSLog(#"%#",sasUrl);
[self postBlobWithUrl:sasUrl Image:[images objectAtIndex:i]];
}];
}
}
I want to use gcd in group somehow to determine that after all the completion blocks is called in a group, it executes Post method. Is there anyway to do this in gcd?
There are many ways you could do this. Here's one:
- (void)storageServiceBlob:(NSArray *)imageFilenames
{
StorageService *storageService = [StorageService getInstance];
__block NSMutableSet *remainingImageFilenames = [NSMutableSet setWithArray:imageFilenames];
for (NSString *imageFilename in imageFilenames) {
NSString *imageName = [self imageNameForImageFilename:imageFilename];
[storageService getSasUrlForNewBlob:imageName forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
[self postBlobWithUrl:sasUrl imageFilename:imageFileName];
[remainingImageFilenames removeObject:imageFilename];
if ([remainingImageFilenames count] == 0) {
// you're done, do your thing
}
}];
}
}
A few tips:
Be careful with your naming. There seems to be some ambiguity there.
Generally, idiomatic method name parameters start with a lower-case letter, e.g. myMethodWithThis:andThat:, not MyMethodWithThis:AndThat:.
Fast enumeration, e.g. for (id obj in array) is your friend. Learn and use it.
You can shortcut [array objectAtIndex:1] as array[1].
If you have access to the queue that the requests are going in then you can issue a barrier block.
When you have an async queue a barrier block will sit and wait to be executed until all of the blocks issued before it have run.
If you don't have access to the queue then your best bet is to keep a count.

Asynchronous Recursive Calls using Box sdk - How to iterate all files in using iOS BOX sdk api

I am trying to iterate all images stored in a Box account.
The following code isn't correct and can't seem to find the error.
The problem seems to be waiting for all of the asynchronous recursive calls to complete in order to know when there are no more images to be fetched in order to signal completion
-(void)enumerateFiles
{
[self recursiveEnumerateFilesAtPath:#"0" completion:nil];
}
-(void)recursiveEnumerateFilesAtPath:(NSString *)folderID completion:(void(^)())block
{
static NSArray *imageExtensions;
imageExtensions = #[#"jpg",#"jpeg",#"png"];
[self enumerateFilesAtPath:folderID completion:^(BoxCollection *collection) {
NSUInteger numberOfItems = collection.numberOfEntries;
for (int i = 0; i < numberOfItems; i++) {
id model = [collection modelAtIndex:i];
if ([model isKindOfClass:[BoxFolder class]])
{
BoxFolder *folder = (BoxFolder *)model;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self recursiveEnumerateFilesAtPath:folder.modelID completion:^{
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
} else if ([model isKindOfClass:[BoxItem class]])
{
BoxItem *item = (BoxItem *)model;
NSString *extension = [[item.name pathExtension] lowercaseString];
if ([imageExtensions containsObject:extension])
{
[self.items addObject:model];
}
}
}
if (block)
{
block();
}
}];
}
-(void)enumerateFilesAtPath:(NSString *)folderID completion:(void(^)(BoxCollection *collection))block
{
BoxCollectionBlock success = ^(BoxCollection *collection)
{
block(collection);
};
BoxAPIJSONFailureBlock failure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSDictionary *JSONDictionary)
{
block(nil);
};
[[BoxSDK sharedSDK].foldersManager folderItemsWithID:folderID requestBuilder:nil success:success failure:failure];
}
The key to a viable approach is to make the inner loop, that is
for (int i = 0; i < numberOfItems; i++) {..}
a construct which sequentially invokes a number of asynchronous tasks and is itself asynchronous. The asynchronous task should be a block (^) which will invoke the outer method and will capture state so that its outer method recursiveEnumerateFilesAtPath: can be invoked in a recursive manner.
Note that this is not a recursion we know from functions, where a function calls itself before it returns, and where the state is hold on the program stack. Here, we rather have an iteration (asynchronous loop) - and there's no state on the stack, instead the state required for the "recursion" is hold in the variables captured in the blocks, and these reside on the heap.
That is, you need to transform the "for loop" (for (int i = 0; i < numberOfItems; i++) {..}) into some asynchronous method, e.g:
- (void) forEach:(NSArray*)objects applyTask:(task_t) completion:(completion_t)completionHandler;
where task is asynchronous, has a completion handler, and (conditionally) calls the outer method recursiveEnumerateFilesAtPath:.
This forEach construct could be implemented using a NSOperationQueue, whose max concurrent operations is set to one, and where the task is a NSOperation with a completion handler. Alternatively - and simpler in this case, it can be implemented using dispatch queues and a dispatch group. Or - even more simpler, it can be implemented using an "asynchronous loop". I'll show a possible implementation, see "NSArray Category forEachApplyTask:completion: below.
Now, suppose there is a Category for NSArray with an asynchronous forEachApplyTask:completion: method:
#interface NSArray (AsyncExtension)
- (void) forEachApplyTask:(unary_async_t)task completion:(completion_t) completion;
#end
This method takes a block of type unary_async_t as first parameter:
typedef void (^unary_async_t)(id input, completion_t completion);
and a completion handler as second parameter:
typedef void (^completion_t)(id result);
This typedef of a completion handler, is generic and can be used for all completion handler variants.
Let's suppose, the block task will be sequentially applied for each element in the receiver (the array). And suppose, this array is an array of "model"s in your original code -- you just need to create an array of "model"s out from the BoxCollection object.
When finished, the completion handler passes a result, which is an array containing the result of each task in the same order.
This task executes the block ({..}) in your former for loop.
Your method recursiveEnumerateFilesAtPath: will become inevitable asynchronous (since to invokes an asynchronous method in its implementation), and thus gets a completion handler parameter as well.
With the given assumptions made above, you can implement your problem as shown below:
-(void)recursiveEnumerateFilesAtPath:(NSString *)folderID
completion:(completion_t)finalCompletionHandler
{
[self enumerateFilesAtPath:folderID completion:^(BoxCollection *collection) {
NSArray* models = ...; // create an NSArray from the collection
[models forEachApplyTask:^(id model, completion_t taskCompletionHandler) {
if ([model isKindOfClass:[BoxFolder class]]) {
BoxFolder *folder = (BoxFolder *)model;
[self recursiveEnumerateFilesAtPath:folder.modelID completion:^(id result){
// result should be #"finished folder"
taskCompletionHandler(#"folder"); // taskCompletionHandler will never be nil
}];
}
else if ([model isKindOfClass:[BoxItem class]]) {
BoxItem *item = (BoxItem *)model;
NSString *extension = [[item.name pathExtension] lowercaseString];
if ([imageExtensions containsObject:extension]) {
[self.items addObject:model];
}
taskCompletionHandler(#"item");
}
}
completion:^(id array){
// this folder is finished. array may be for example #[#"folder", #"item", #"item"]
if (finalCompletionHandler) {
finalCompletionHandler(#"finished folder");
}
}];
}];
}
Caution: not tested, you may experience issues!
Note, that everything runs asynchronous, and that there is no recursion whose state is put on the stack. The required state for recursively iterating the folders is put on the heap, enclosed in captured variables in the block.
The NSArray Category method forEachApplyTask:completion: is a reusable component, which can be utilized in many cases. One can implement it as follows:
Implementation of Category NSArray
/**
Objective:
Asynchronously transform or process an array of items - one after the
other and return the result of each transform in an array.
Synopsis:
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion);
*/
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
/**
Typedef of a generic completion handler for an asynchronous task.
The parameter _result_ is the eventual result of the asynchronous
task. In case the task has been failed _result_ SHALL be an
NSError object.
*/
typedef void (^completion_t)(id result);
/**
Typedef for an asynchronous "transform" function. It's an
unary block taking an input as parameter and signals
the eventual result via a completion handler.
*/
typedef void (^unary_async_t)(id input, completion_t completion);
/**
`transform_each` sequentially applies an asynchronous transform function
to each object in the input array _inArray_ and signals the result as an
array containing the result of each transform applied to the input object.
Function `transform_each` is itself a asynchronous function, that is,
its eventual result will be signaled to the client through a completion
handler.
The result array contains the transformed objects in order of the
corresponding input array.
*/
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion);
// implementation
static void do_each(NSEnumerator* iter, unary_async_t task, NSMutableArray* outArray, completion_t completion)
{
id obj = [iter nextObject];
if (obj == nil) {
if (completion)
completion([outArray copy]);
return;
}
task(obj, ^(id result){
[outArray addObject:result];
do_each(iter, task, outArray, completion);
});
}
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion) {
NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]];
NSEnumerator* iter = [inArray objectEnumerator];
do_each(iter, task, outArray, completion);
}
/*******************************************************************************
Example
*******************************************************************************/
// A Category for NSArray
#interface NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(unary_async_t) task completion:(completion_t) completion;
#end
#implementation NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(unary_async_t) task completion:(completion_t) completion {
transform_each(self, task, completion);
}
#end
See also: https://gist.github.com/couchdeveloper/6155227

Resources