HTTP call runs first time but then waits forever - ios

I'm using SDWebImage to load images asynchronously. I want to combine comments and photos in a dictionary like below:
- (NSArray *) fetchPhotos:(NSArray *) requestedPhotoArray
{
NSMutableArray *photos;
UIImageView *imageview;
for (requestedPhoto in requestedPhotoArray) {
[imageview setImageWithURL:[NSURL URLWithString: requestedPhoto.url]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
NSDictionary *parameters = #{ #"function":GET_COMMENT,
#"photoId":requestedPhoto.photoID,
}
NSArray *comments = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
Photo *photo = [Photo photoWithDictionary:
#{#"imageView": imageview,
#"comments" : comments,
}];
[photos addObject:photo];
}
return photos;
}
But fetchPhotos function makes one http call and waits forever and then nothing returns.
fetchPhotos is called like below (simplified version):
NSDictionary *parameters = #{#"function":GET_PHOTO_INFO_ARRAY,
#"userid":3,
#"pageid":99,
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
requestedPhotosInfoArray = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
dispatch_async(dispatch_get_main_queue(), ^{
[self fetchPhotos: requestedPhotosInfoArray];
}
communicator:HTTPRequestWithParams do a HTTP request like below
...
__block id result;
dispatch_queue_t queue = dispatch_queue_create("my_queue", 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async( queue, ^{
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
result = responseObject;
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
result = error;
dispatch_semaphore_signal(semaphore);
}
];
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return result;
Any idea why fetchPhotos only returns first data and waits forever?
UPDATE:
I realised that it waits in
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
then I add a async dispatch queue in fetchPhotos
for (requestedPhoto in requestedPhotoArray) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[imageview setImageWithURL:[NSURL URLWithString: requestedPhoto.url]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
NSDictionary *parameters = #{ #"function":GET_COMMENT,
#"photoId":requestedPhoto.photoID,
}
NSArray *comments = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
....
It's not waiting forever now but it doesn't make a http call.

I used block callback instead of semaphores
dispatch_async( queue, ^{
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure) {
failure(error);
}
}
];
});

Related

SVProgressHUD dismiss not working in custom class

Firstly, I've added some functions which need to call from different ViewControllers in one class file. For those function, I've add SVProgressHUD as preprocess and dismiss after all process is finished. SVProgressHUD is displaying correctly before process is started. But after process, SVProgressHUD is never dismissed at all. Please let me know how to solve that issue.
I've added [SVProgressHUD dismiss]; in all process is finished. But never dismissed.
common.m
- (NSDictionary *) loadBlahClass:(NSString *)paramUserId {
[SVProgressHUD showWithStatus:#"Loading Cards..."];
__block NSDictionary *jsonResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.responseSerializer.acceptableContentTypes setByAddingObject:#"application/json"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *urlStr = [NSString stringWithFormat:#"MY_URL"];
[manager GET:urlStr parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *jsonString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[SVProgressHUD dismiss];
dispatch_semaphore_signal(semaphore);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
[SVProgressHUD dismiss];
[self showAlert:APP_NAME alertMessage:[error localizedDescription]];
dispatch_semaphore_signal(semaphore);
}];
[SVProgressHUD dismiss];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return jsonResponse;
}
testViewController.m
_allTableData = [[NSMutableArray alloc]init];
NSDictionary *jsonResponse = [commClass loadBlahClass:strUserId];
NSNumber *status = [jsonResponse valueForKey:#"Success"];
NSString *message = [jsonResponse valueForKey:#"Message"];
NSArray *dataArray = [jsonResponse valueForKey:#"lstCC"];
if([status intValue] == 1) {
for(int i=0; i<[dataArray count]; i++) {
//working .......
}
[_ccTable reloadData];
[SVProgressHUD dismiss];
} else {
[commClass showAlert:APP_NAME alertMessage:message];
[SVProgressHUD dismiss];
}
I've found answer. Something like that.
[SVProgressHUD show];
__block BOOL result;
dispatch_async(queue, ^{
result = [self autanticate];
NSLog(#"autantication result = %d", result);
result = [self getCSRFToken];
NSLog(#"Login success result = %d", result);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
})
});

dispatch_group_notify does not wait for one dispatch_group_async

Two concurrent background tasks need to patch two separate arrays which need to be merged in a dispatch_group_notify block.The problem is that.the first block is exceeded but the dispatch_group_notify is exceeded without waiting for the execution of the second background task.
The only different between them is that the first one make a local search and the second one makes a remote call to a web service.Any clue why the second one is jumped over ?
Edit: I also tried the approach mentioned in https://stackoverflow.com/a/19580584/859742 using dispatch_barrier_async but still same.
dispatch_group_t taskGroup = dispatch_group_create();
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__block NSArray *localAddresses;
__block NSArray *remoteAddresses;
//Get local array in the background
dispatch_group_async(taskGroup, mainQueue, ^{
//localAddresses is set
});
//get remote array from server
dispatch_group_async(taskGroup, mainQueue, ^{
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
});
//Merge two arrays
dispatch_group_notify(taskGroup, mainQueue, ^{
//remoteAddresses and local addresses are merged
});
And the remote search method looks like this
- (void)searchForPlacesContainingText:(NSString *)searchText
location:(CLLocation *)alocation
completion:(MDAddressManagerBlock)completionBlock
{
NSDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:searchText forKey:#"input"];
[[MDHTTPClient sharedHTTPClient] getPath:#"v1/remotePlaces.json"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id dict) {
if ([MDHTTPClient isResponseValid:dict])
{
completionBlock(returnArray, nil);
}
else
{
completionBlock(nil, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
EDLog(#"%#", error);
completionBlock(nil, [MDError errorAFNetworking:error]);
}];
}
This is because your getPath method runs asynchronously. You need it to not leave the group until that completion block runs. So rather than than doing dispatch_group_async, you should manually dispatch_group_enter and dispatch_group_leave.
You can change your code from:
dispatch_group_async(taskGroup, mainQueue, ^{
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
}];
});
To:
dispatch_group_enter(taskGroup);
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
dispatch_group_leave(taskGroup);
});
That will ensure that you don't leave the group until the completion block is called.
Alternatively, you could change searchForPlacesContainingText to use dispatch_group_t parameter:
- (void)searchForPlacesContainingText:(NSString *)searchText
location:(CLLocation *)alocation
group:(dispatch_group_t)group
completion:(MDAddressManagerBlock)completionBlock
{
dispatch_group_enter(group);
NSDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:searchText forKey:#"input"];
[[MDHTTPClient sharedHTTPClient] getPath:#"v1/remotePlaces.json"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id dict) {
if ([MDHTTPClient isResponseValid:dict])
{
completionBlock(returnArray, nil);
}
else
{
completionBlock(nil, nil);
}
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
EDLog(#"%#", error);
completionBlock(nil, [MDError errorAFNetworking:error]);
dispatch_group_leave(group);
}];
}
and change your invocation so that it doesn't do dispatch_group_async, but rather just passes the taskGroup parameter:
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
group:taskGroup
completion:^(NSArray* addresses, MDError *error) {
//remoteAddresses is set
});

How to tell if blocks in loop all have completed executing?

I have a loop set up that downloads a series a images which I will later use for to animate using the animationImages property of UIImageView. I would like to know when all the blocks inside my loops have finished executing so I could begin the animation, and was wondering how I may be able to tell when they are finished completing? Thanks!
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
}];
}
//When I know all the blocks have finished downloading, I will then to animate the downloaded images.
Edit: having issue with Error -999
I am encountering the following issue when executing the code in the provided answer: Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)"
A quick search reveals that Error -999 means "another request is made before the previous request is completed" ... which is certainly the case here since I am making several requests in quick succession. The recommended fix suggested here didn't work for me as it will only successfully download one UIImage (the last one requested) , with the previous ones failing. I was wondering if there is workaround here or in AFNetworking that I ought to consider? Thanks!
Edit 2: working code based on #David's solution
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:imageRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
dispatch_group_enter(group);
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response: %#", responseObject);
UIImage *retrivedImage = (UIImage *)responseObject;
[self.downloadedUIImages addObject:retrivedImage];
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
dispatch_group_leave(group);
}];
[requestOperation start];
counter ++;
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Horray everything has completed");
NSLog(#"What is here %#", self.downloadedUIImages);
NSLog(#"Done");
});
Create a dispatch group, in the for loop enter the group, in the completion block leave the group. Then you can use dispatch_group_notify to find out when all blocks have completed:
dispatch_group_t group = dispatch_group_create();
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
dispatch_group_enter(group);
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
dispatch_group_leave(group);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do your completion stuff here
});
Count how many you've completed. The challenging part is making it thread safe. I recommend creating an atomic counter class for that.
Generic solution!
+ (void)runBlocksInParallel:(NSArray *)blocks completion:(CompletionBlock)completion {
AtomicCounter *completionCounter = [[AtomicCounter alloc] initWithValue:blocks.count];
for (AsyncBlock block in blocks) {
block(^{
if ([completionCounter decrementAndGet] == 0) {
if (completion) completion();
}
});
}
if (blocks.count == 0) {
if (completion) completion();
}
}
NSMutableArray *asyncBlocks = [NSMutableArray array];
for (PFObject *pictureObject in objects){
[asyncBlocks addObject:^(CompletionBlock completion) {
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
} completion:completion];
}];
}
[BlockRunner runBlocksInParallel:[asyncBlocks copy] completion:^{
//Do your final completion here!
}];
Set up a property and initialize it to the number of cycles - objects.count. In the completion of the block, lower the number down. When you reach zero, you are done.
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
if([[objects lastObject] isEqual:pictureObject]) {
[self animateImages];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
if([[objects lastObject] isEqual:pictureObject]) {
[self animateImages];
}
}];
}
- (void)animateImages {
//do animation here.
}

AFNetworking cancel successBlock

I want to stop/cancel the operation in case of running the request again. Method cancelAllHTTPOperationsWithMethod is working ok, but i have a problem when AFNetworking has already fetched the results and my successBlock is being fired - I want to stop it in the nearest future. But the problem is that operation.isCancelled is not cancelled.
The question is do i have to perform my 'very long successBlock' in NSOperation and cancel them too or is there any easier and faster method?
Code:
[[AFHTTPClient sharedInstance] cancelAllHTTPOperationsWithMethod:#"GET" path:#"path"];
[[AFHTTPClient sharedInstance] getPath:#"path" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
for (longLoop) {
// do something long
if (self.isCancelled) return; // this won't fire no matter how often i run it
}
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// do something to fail
}];
I've ended with doing NSOperation inside. Something like:
[[AFHTTPClient sharedInstance] cancelAllHTTPOperationsWithMethod:#"GET" path:#"path"];
[operationQueue cancelAllOperations];
[[AFHTTPClient sharedInstance] getPath:#"path" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// add new operation
MyOperation *successOperation = [[MyOperation alloc] init];
[successOperation setResponseObject:responseObject];
[operationQueue addOperation:successOperation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// call delegate
[self didFailFetchDataWithError:error];
}];

MBProgressHUD is not changing to customView

I am running around the issue, that MBProgressHUD should be updating its View with a Checkmark/X for succeeded/failed requests. Somehow this doesnt really work as intended and the update only works after all the code has executed.
initializing the HUD
...
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Verifying Credit Card";
_HUD = hud;
CWAPIClient *client = [CWAPIClient sharedClient];
client.delegate = self;
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.clubw.billing", 0);
dispatch_async(backgroundQueue, ^{
[client save:billingProfile with:[Address defaultBillingAddress]];
});
....
Callback to process information:
self POST:[NSString stringWithFormat:#"user/%#/billingprofile", [[Profile defaultProfile] userId]] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate CWAPIClient:self doneVerifyingCreditCard:responseObject];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
callback when action is complete
BOOL success = [[jsonResponse objectForKey:#"success"] boolValue];
if (success)
{
NSLog(#"Thread: %#", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *checkmarkImage = [UIImage imageNamed:#"checkmark.png"];
UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmarkImage];
_HUD.customView = checkmarkView;
_HUD.labelText = #"Credit Card Verified!";
_HUD.mode = MBProgressHUDModeCustomView;
NSLog(#"Thread: %#", [NSThread currentThread]);
sleep(5);
[_HUD hide:YES];
});
After the 5 seconds, it simply closed the HUD - and the updated one flashes for a second.
It seems, like this is a threading issue - but I cant seem to figure out where it is throwing up.
In the portion of the code where you make your callback, be sure to perform the callback on the main queue as well.

Resources