Dispatch 100 HTTP Request in order - ios

I am using objective-C to write an app which needs to dispatch 100 web request and the response will be handled in the call back. My question is, how can I execute web req0, wait for call back, then execute web req1 and so on?
Thanks for any tips and help.
NSURL *imageURL = [[contact photoLink] URL];
GDataServiceGoogleContact *service = [self contactService];
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSMutableURLRequest *request = [service requestForURL:imageURL
ETag: nil
httpMethod:nil];
[request setValue:#"image/*" forHTTPHeaderField:#"Accept"];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 0.3;
fetcher.minRetryInterval = 0.3;
[fetcher setAuthorizer:[service authorizer]];
[fetcher beginFetchWithDelegate:self
didFinishSelector:#selector(imageFetcher:finishedWithData:error:)];
}
- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
if (error == nil) {
// got the data; display it in the image view. Because this is sample
// code, we won't be rigorous about verifying that the selected contact hasn't
// changed between when the fetch began and now.
// NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
// [mContactImageView setImage:image];
NSLog(#"successfully fetched the data");
} else {
NSLog(#"imageFetcher:%# failedWithError:%#", fetcher, error);
}
}

You can't simply call this code in a loop as GTMHTTPFetcher works asynchronously so the loop, as you see, will iterate and start all instances without any delay.
A simple option is to put all of the contacts into a mutable array, take the first contact from the array (remove it from the array) and start the first fetcher. Then, in the finishedWithData callback, check if the array contains anything, if it does remove the first item and start a fetch with it. In this way the fetches will run serially one after the other.
A better but more complex solution would be to create an asynchronous NSOperation (there are various guides on the web) which starts a fetch and waits for the callback before completing. The benefit of this approach is that you can create all of your operations and add them to an operation queue, then you can set the max concurrent count and run the queue - so you can run multiple fetch instances at the same time. You can also suspend the queue or cancel the operations if you need to.

Related

Multiple POST request using ASIFormDataRequest synchronous

Basically my app will retrieve an array of Data from database and upload it to the server(one at a time/ one after the other). I want to stop all the request when one of the data failed to upload (please check on the comments).
Code:
// 0 means need to upload to server
NSString *condition = [NSString stringWithFormat:#"isUploaded=\"0\""];
// array of ID on my database to be uploaded
NSArray *arrayOfID = [Registered distinctValuesWithAttribute:#"registeredID" predicate:[NSPredicate predicateWithFormat:condition]];
// loop every index and upload it to server
for (int i=0; i<arrayOfID.count && !isBreak; i++) {
// get the entity using ID
NSString *condition = [NSString stringWithFormat:#"registeredID=\"%#\"",[arrayOfID objectAtIndex:i]];
Registered *entity = [Registered getWithPredicate:[NSPredicate predicateWithFormat:condition]];
if (entity) {
__weak ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[self setRequest:request withEntity:entity]; // set delegate,POST,etc.
[request setCompletionBlock:^{
// returns dictionary (success/failed)
NSDictionary *dict = [[request responseString] JSON];
if ([[dict valueForKey:#"status"] isEqualToString:#"success"]) {
// set IsUploaded to 1 after successful upload to server
[entity setIsUploaded:[NSNumber numberWithInt:1]];
[Registered commit];
// Any necessary ideas that would make my code better
// and continue the POST request and proceed to the next entity???
}
else {
// I want to cancel all the request here and get out to this loop
}
}];
[request setFailedBlock:^{
// I want to cancel all the request here and get out to this loop
}];
// start startSynchronous
[request startSynchronous];
} else {
[GlobalMethods ShowAlertView:#"Database Error" message:#"Please try again later"];
isBreak=YES;
}
}
You could tweak your code slightly and use the provided ASINetworkQueue class. From the documentation, if one request in the queue fails, by default the rest are cancelled automatically.
You can use a break statement to immediately exit a loop. However, because the callbacks are asynchronous, putting one in the failure block will not have the effect you're looking for...assuming the failure block is fired in reaction to a server response and not something in the API itself, that loop will complete and fire off all your requests--and the enclosing method will return--long before any of your requests has the time to come back from the server and call its failure block (this is networking code, after all). So you're not going to be able to use a failure block to interrupt the creation of additional requests in the loop; that's not how asynchronous calls work.

Force app to wait for method completion (data download)

I'm working with an app that requests data from an OAuth2.0 protected server. When I use the GTM OAuth Library to retrieve data, the program continues to run while the data is being downloaded in the background. I need some sort of mechanism to either force my application to wait until the didFinishWithData selector is called,or I need a way to notify my ViewController of the download's completion, so I can then utilize the data immediately.
I've tried conditional blocks, but those aren't doing it for me. I've also tried polling the object whose data I'm interested in, but if I do that, the data never seems to download. I've heard I can somehow utilize the Notification Center to accomplish this task, so I'll look more into that while I'm waiting for replies here.
Here is basically what is going on:
-(void) getAlert{
// Define the URL of the API module we'd like to utilize.
NSURL *url = [[NSURL alloc] initWithString:#"https://access.active911.com/interface/open_api/api/alerts"];
// Constructs a an HTTP request object to send to the server in order to obtain data.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setValue:#"1" forHTTPHeaderField:#"alert_days"];
// This fetcher sends the request along with the authentication header in a recognizable manner.
GTMHTTPFetcher *fetcher = [[GTMHTTPFetcher alloc] initWithRequest:request];
// Attach the OAuth credentials for the fetcher's use.
[fetcher setAuthorizer:auth];
// Execute the operation.
[fetcher waitForCompletionWithTimeout:10];
NSLog(#"About to get alert");
[fetcher beginFetchWithDelegate:self didFinishSelector:#selector(responseHandler:finishedWithData:finishedWithError:)];
NSLog(#"got alert");
}
-(void)responseHandler:(id)valueNotUsed finishedWithData:(NSData *)data finishedWithError:(NSError *)error{
// Retrieve the server data in a usable object
// All that's being done here is conversion to an NSDictionary
// followed by the creation of subdictionaries from that dictionary
// until our final value can be picked directly out of the resulting dict
NSData *jsonData = [[NSData alloc] initWithData:data];
NSError *dictError;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:jsonData //1
options:kNilOptions
error:&dictError];
NSDictionary *token = [json objectForKeyedSubscript:#"message"];
NSArray *alerts = [token objectForKeyedSubscript:#"alerts"];
NSDictionary *alertData = alerts[0];
mapCode = [alertData objectForKeyedSubscript:#"map_code"];
NSString *city = [alertData objectForKeyedSubscript:#"city"];
NSLog(#"Map code: '%#' with city '%#' and access token %#", mapCode, city, accessToken);
}
And I need to pass the mapCode to my view controller.
Thanks for the help!
First off, please rethink about having the UI halt while you fetch results from the server. This can create an extremely bad UX for the app and only should be done if absolutely necessary.
Second, does your responseHandler method work? And do you only need mapCode in the VC that responseHandler is in?
If so, you don't even need to use Notifications. Simply do:
-(void)responseHandler:(id)valueNotUsed finishedWithData:(NSData *)data finishedWithError:(NSError *)error{
...
...
mapCode = [alertData objectForKeyedSubscript:#"map_code"];
[self updateVCWithMapCode:mapCode];
}
That will call the method after the response has been received. Passing it explicitly too so you don't need to have mapCode be a property as well.

Amazon S3 track downloaded file in Asynchronous method in iOS

During downloading multiple files using following asynchronous method of AWS iOS, I want to keep track which image is downloaded.
Following code is used for asynchronous image download.
S3TransferManager *tm = [S3TransferManager new];
S3TransferOperation *downloadFileOperation = [tm downloadFile:storeFilePath bucket:Bucket key:downloadPath];
Above method is in for loop, so there could be n images to download.
Delegate method which notify image is downloaded
-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response { }
But I did not find anything, using which I can manage that which actual image is downloaded. whether it was the firstID image or second one.
Any idea on where I can put some extra data , which can be received on image download ?
You can set requestTag:
S3PutObjectRequest *putObjectRequest = [ [ S3PutObjectRequest alloc ] initWithKey:keyFile inBucket:self.s3BucketName ];
putObjectRequest.requestTag = urlStringFile;
putObjectRequest.filename = fileName;
and then analyse it:
- (void)request:(AmazonServiceRequest*) request didCompleteWithResponse:(AmazonServiceResponse*) response
{
NSLog(#"Upload finished. RequestTag = %#", request.requestTag);
}
As far as, I have to only download content from Amazon s3, I used ASIHTTPRequest.
(Though this is not maintained since 2011, but I found its very useful and easy to use for my app).
Code Example,
// Initialize network Queue.
ASINetworkQueue *networkQueue = [[ASINetworkQueue alloc] init];
[networkQueue reset];
[networkQueue setRequestDidFinishSelector:#selector(requestDone:)]; //This is where download completion will be notified.
//Initialize Request.
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:#"Bucket_Name" key:#"/Path/file"];
This is what I was looking, I need all the information about what I am downloading on download completion. This userInfo contains all the data, which is available on download completion.
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"Data", #"Key", nil];
request.userInfo = userInfo;
userInfo = nil;
// End of userInfo set.
[networkQueue addOperation:request]; // add request in ASINetworkQueue object. We can also add multiple request here.
And last,
[networkQueue go]; // This will start downloading.
// Delegate method, where download completion will be notified
- (void)requestDone:(ASIS3Request *)request
{
NSLog(#"UserInfo : %#", request.userInfo); // Request data, to manage which request is complete.
}
Done.

Having trouble with multiple NSURLConnection

I've looked around a lot and cant seem to find a proper answer for my problem. As of now I have a network engine and I delegate into that from each of the view controllers to perform my network activity.
For example, to get user details I have a method like this:
- (void) getUserDetailsWithUserId:(NSString*) userId
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#Details", kServerAddress]]];
request.HTTPMethod = #"POST";
NSString *stringData = [NSString stringWithFormat:#"%#%#", kUserId, userId];
NSData *requestBodyData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestBodyData;
NSURLConnection *conn = [[NSURLConnection alloc] init];
[conn setTag:kGetUserInfoConnection];
(void)[conn initWithRequest:request delegate:self];
}
And when I get the data in connectionDidFinishLoading, I receive the data in a NSDictionary and based on the tag I've set for the connection, I transfer the data to the required NSDictionary.
This is working fine. But now I require two requests going from the same view controller. So when I do this, the data is getting mixed up. Say I have a connection for search being implemented, the data from the user details may come in when I do a search. The data is not being assigned to the right NSDictionary based on the switch I'm doing inside connectionDidFinishLoading. I'm using a single delegate for the entire network engine.
I'm new to NSURLConnection, should I setup a queue or something? Please help.
EDIT
Here's the part where I receive data in the connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if ([connection.tag integerValue] == kGetUserDetails)
networkDataSource.userData = self.jsonDetails;
if ([connection.tag integerValue] == kSearchConnection)
networkDataSource.searchData = self.jsonDetails;
}
and after this I have a switch case that calls the required delegate for the required view controller.
Anil here you need to identify for which request you got the data,
simplest way to check it is as below,
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
// Check URL value for request, url for search and user details will be different, put if condition as per your need.
conn.currentRequest.URL
}
Try using conn.originalRequest.URL it will give original request.
You can do in many ways to accomplish your task as mentioned by others and it will solve your problem . But if you have many more connections , you need to change your approach.
You can cretae a subclass of NSOperation class. Provide all the required data, like url or any other any informmation you want to get back when task get accomplish , by passing a dictionary or data model to that class.
In Nsoperation class ovewrite 'main' method and start connection in that method ie put your all NSURRequest statements in that method. send a call back when download finish along with that info dict.
Points to be keep in mind: Create separte instance of thet operation class for evey download, and call its 'start method'.
It will look something like :
[self setDownloadOperationObj:[[DownloadFileOperation alloc] initWithData:metadataDict]];
[_downloadOperationObj setDelegate:self];
[_downloadOperationObj setSelectorForUpdateComplete:#selector(callBackForDownloadComplete)];
[_downloadOperationObj setQueuePriority:NSOperationQueuePriorityVeryHigh];
[_downloadOperationObj start];
metaDict will contain your user info.
In DownloadFileOperation class you will overwrite 'main' method like :
- (void)main {
// a lengthy operation
#autoreleasepool
{
if(self.isCancelled)
return;
// //You url connection code
}
}
You can add that operation to a NSOperationQueue if you want. You just need to add the operation to NSOperationQueue and it will call its start method.
Declare two NSURLConnection variables in the .h file.
NSURLConnection *conn1;
NSURLConnection *conn2;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if(connection == conn1){
}
else if(connection == conn2){
}
}

How can I perform a background check on iOS?

I am needing to perform a very simple background check for my iOS app. It needs to just make one call to my web server and check the number it retrieves against something in my app. Is it possible to do that kind of background check? If so what can I do to put it together?
EDIT
To clarify what I mean by background: I am meaning background on the phone. When the app is not present. Is it possible to do this request in the background? Obviously with the app not being completely closed out from multitasking.
This sounds like the perfect sort of thing for NSOperationQueue.
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
You can write an operation and then put it on the queue when you need it.
Alternatively, and more simply, you can just do a really simple asynchronous call.
+ (NSArray *) myGetRequest: (NSURL *) url{
NSArray *json = [[NSArray alloc] init];
NSData* data = [NSData dataWithContentsOfURL:
url];
NSError *error;
if (data)
json = [[NSArray alloc] initWithArray:[NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error]];
if (error)
NSLog(#"%#", error)
return json;
}
and then put it in a simple dispatch block...
dispatch_queue_t downloadQueueA = dispatch_queue_create("updater", NULL);
dispatch_async(downloadQueueA, ^{
// stuff done here will happen in the background
NSArray * arrayOfData = [self myGetRequest: myURL];
// could be array... dictionary... whatever, you control this by returning the type of data model you want from the server formatted as JSON data
NSString * stringValue = arrayOfData[index];
dispatch_async(dispatch_get_main_queue(), ^{
// perform checking here and do whatever updating you need to do based on the data
});
});
There are many way to check your server and retrieve data.
Here my suggestion:
Create the file containing your data on the server (e.g. Data.txt)
Use NSURLRequest to create a request to Data.txt
Use connectionDidFinishLoading to get data from Data.txt
Put data from Data.txt in a NSArray
Work/compare the array and do your logic
If your server is fast and you have to get just one number, you can do it in the main tread, otherwise use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// your request here
});
to work in a different tread as requested.
And remember to check if internet connection and your server are available with something like Reachability and manage connection error with NSURLRequest delegate
You should be able to do that using Grand Central Dispatch: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
Take a look at this tutorial Multithreading and Grand Central Dispatch on iOS.
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial

Resources