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.
Related
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
});
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);
}
}
];
});
my code is loading 7 pics from url and adding their data to an array. in the end of the process I do get an 8 objects array, but I'm trying to show a progress bar until the process of loading all the photos finished.
I do not have an idea how to do that...
here is the code
-(void)SetUpDrinks
{
loadingView.hidden=NO;
[loadingView_activity startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible=YES;
imgsDATA = [[NSMutableArray alloc] init];
for (int i=0; i<8; i++) {
imageDownloadNum++;
absPath = [NSString stringWithFormat:#"http://domain/app/menu/drinks/%i.png",imageDownloadNum];
trimmedAbsPath = [absPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL *imgURL = [NSURL URLWithString:trimmedAbsPath];
NSLog(#"%#",imgURL);
imgDATA = [[NSData alloc] initWithContentsOfURL:imgURL];
[imgsDATA addObject:imgDATA];
}
dispatch_async(dispatch_get_main_queue(), ^{
loadingView.hidden=YES;
[loadingView_activity stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
[self RefreshImg];
});
});
}
You could make an NSOperation subclass to download your image and then use a dispatch_group. Dispatch groups are a way to block a thread until one or more tasks finish executing - in this scenario we are waiting for all of your downloads to finish.
You can now use the progressBlock to update the UI and let the user know how many of the images have finished downloading.
If you want progress for an individual download then take a look at the NSURLConnection reference. In particular - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)enqueueGroupOfOperations:(NSArray *)operations
progress:(void (^)(NSUInteger completedCount, NSUInteger totalOperations))progressBlock
completion:(void (^)(NSArray *operations))completionBlock;
{
NSParameterAssert(operations);
NSParameterAssert(progress);
NSParameterAssert(completion);
__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *dependentOperation = [NSBlockOperation blockOperationWithBlock:^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completion(operations);
});
dispatch_release(group);
}];
for (NSOperation *operation in operations) {
operation.completionBlock = ^{
dispatch_group_async(group, dispatch_get_main_queue(), ^{
NSUInteger count = [[operations indexesOfObjectsPassingTest:^BOOL(NSOperation *operation, NSUInteger idx, BOOL *stop) {
return [operation isFinished];
}] count];
progress(count, [operations count]);
dispatch_group_leave(group);
});
};
dispatch_group_enter(group);
[dependentOperation addDependency:operation];
}
[self.operationQueue addOperations:operations waitUntilFinished:NO];
[self.operationQueue addOperation:dependentOperation];
}
If this is too much for you then you can go over to AFNetworking where this is all done for you, https://github.com/AFNetworking/AFNetworking. But its always nice to know how some of this stuff works.
I've built a Rails backend for an iOS app that lets users access a RESTful API only after having authorized the device. Basically authorization is managed by retrieving a token.
When the user submits username and password, the webservice gets called with an AFHTTPRequestOperation (see code below). I also display to the user a HUD (MBProgressHUD) to track the progress of the request. I've set up callbacks for success and failure and I want to update the HUD and let it stick onscreen with an updated message for a couple of seconds before dismissing it.
//Set HUD
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeIndeterminate;
hud.labelText = #"Authenticating";
//Set HTTP Client and request
NSURL *url = [NSURL URLWithString:#"http://localhost:3000"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient setParameterEncoding:AFFormURLParameterEncoding]; //setting x-www-form-urlencoded
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:#"/api/v1/tokens.json" parameters:#{#"password":_passwordField.text, #"email":_emailField.text}];
//Set operation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//Success and failure blocks
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
NSError *error;
NSDictionary* jsonFromData = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
NSLog(#"%#", jsonFromData);
_statusLabel.text = #"Device authenticated!";
hud.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"37x-Checkmark.png"]];
hud.mode = MBProgressHUDModeCustomView;
hud.labelText = #"Authenticated!";
sleep(2);
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"Error");
sleep(2);
_statusLabel.text = #"Wrong username or password!";
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}];
When the success / failure operation callbacks get called:
I try to update the HUD mode and text;
I wait sleep() for a couple of seconds;
I dismiss the HUD [MBProgressHUD hideAllHUDsForView:self.view animated:YES];;
I've also tried using dispatch_queue_t dispatch_get_main_queue(void); and run the HUD update on the main thread but to no avail.
Any ideas on what am I getting wrong?
I am currently developing an iOS app that allows a user to post a classified listing for other users to see. On all devices, that app hangs in a "O% Uploaded" SVProgressHUD for a while before displaying "Our server is temporarily unavailable. Please try again later.", which is what I have coded in the case of a Status Code 500 error from the server side. However, on the iOS simulator, everything works smoothly.
All other functionality involving network requests is working perfectly except for this, so it must have something to do with the actual upload process. I've posted the corresponding code below; if you require anything additional to help figure this out, please let me know.
Networking Code
- (IBAction)publishPressed:(UIBarButtonItem *)sender {
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeBlack];
// Set the params for the API call in an NSMutableDictionary called mutableParams.
// Create the form request with the post parameters and image data to pass to the API.
NSMutableURLRequest *request = [[UAPIClient sharedClient] multipartFormRequestWithMethod:#"POST"
path:#"mobPost.php"
parameters:mutableParams
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
if (self.imageHasBeenSet) {
[self.listingImageView.image fixOrientation];
NSData *imageData = UIImagePNGRepresentation(self.listingImageView.image);
[formData appendPartWithFileData:imageData name:#"userfile"
fileName:#"postImage.png" mimeType:#"image/png"];
}
}];
// Create the `AFJSONRequestOperation` from the form request, with appropriate success and failure blocks.
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseJSON) {
if ([[responseJSON objectForKey:#"status"] intValue] == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:#"Success!"];
});
// Pass a message back to the delegate so that the modal view controller
// can be dismissed successfully.
[self.delegate uCreateListingTableViewController:self didCreateListing:YES];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Post failed. Please try again."];
});
NSLog(#"Status %#", [responseJSON objectForKey:#"status"]);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Our server is temporarily unavailable. Please try again later."];
});
}];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:[[NSString stringWithFormat:#"%lli", (totalBytesWritten / totalBytesExpectedToWrite)] stringByAppendingString:#"% Uploaded"]];
});
}];
[[UAPIClient sharedClient] enqueueHTTPRequestOperation:operation];
}
UAPIClient.m
#import "UAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const kUAPIBaseURLString = #"hiding string for privacy";
#implementation UAPIClient
+ (UAPIClient *)sharedClient {
static UAPIClient *_sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[UAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kUAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
#end