I am using the great RestKit Framework for an iPhone Application.
I have got a method, where I send requests to a webservice. Sometimes four or more requests per 30 seconds.
My sendMethod looks like:
- (void) sendLocation {
NSString *username = [userDefaults objectForKey:kUsernameKey];
NSString *password = [userDefaults objectForKey:kPasswordKey];
NSString *instance = [userDefaults objectForKey:kInstanceKey];
NSString *locationname = self.location.locationname;
NSString *url = [[NSString alloc] initWithFormat:#"http://www.someadress.com/%#", instance];
RKClient *client = [RKClient clientWithBaseURL:url username:username password:password];
// Building my JsonObject
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys: username, #"username", locationname, #"locationname", nil];
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjectsAndKeys:locationDictionary, #"location", nil];
NSString *JSON = [jsonDictionary JSONRepresentation];
RKParams *params = [RKRequestSerialization serializationWithData:[JSON dataUsingEncoding:NSUTF8StringEncoding]MIMEType:RKMIMETypeJSON];
[client post:#"/locations" params:params delegate:self];
}
Sometimes (especially when sending more requests after another) the value of the count property of the RKRequestQueue Object is > 1.
When my application enters background and then enters foreground the requests in the queue (when foreground is entered) are sent to my Webservice and the delegate
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {)
for all requests will be called.
So the question is:
Why doesnt RestKit send some requests immediately (my Webservice doesnt receive anything while a request is stored in the queue)???
Does anyone know a solution or had/has the same problem?
I noticed this line where you create the RKClient:
RKClient *client = [RKClient clientWithBaseURL:url username:username password:password];
This basically creates new instance each time sendLocation method is called - are you sure this is your desired behavior? If the url, username and password does not change, you can access previously created client by calling [RKClient sharedClient]. In your current approach, new request queue is created for each new client.
Now back to the point. Take a look on this property of the RKRequestQueue:
/**
* The number of concurrent requests supported by this queue
* Defaults to 5
*/
#property (nonatomic) NSUInteger concurrentRequestsLimit;
As you can see this defaults to 5, so if you have more than that in any of your queues they will wait until the ongoing requests are processed. You also mentioned that the requests stack up when the application is moved to background, and then when it enters the foreground all of them are dispatched. You can control how your requests behave like this:
- (void)backgroundUpload {
RKRequest* request = [[RKClient sharedClient] post:#"somewhere" delegate:self];
request.backgroundPolicy = RKRequestBackgroundPolicyNone; // Take no action with regard to backgrounding
request.backgroundPolicy = RKRequestBackgroundPolicyCancel; // If the app switches to the background, cancel the request
request.backgroundPolicy = RKRequestBackgroundPolicyContinue; // Continue the request in the background
request.backgroundPolicy = RKRequestBackgroundPolicyRequeue; // Cancel the request and place it back on the queue for next activation
}
I have found this solution here. Scroll down to Background Upload/Download section.
Related
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.
I have an NSTimer in an iOS that is polling a database server at every 10th seconds for a data row in a table based upon a certain data ID, that has been sent as an argument via a PHP-script. If the data ID matches the data ID of the row that have been inserted by an external source then the app will show an alert box containing the information from the data row and the NSTimer will stop to tick.
But this only works while the app is running in the foreground and I want to show the information message as a local notification so that even though the user has exited from the app, it will still poll the server when the app is running in the background as well.
I have read on many sites that the Local Notification service and background fetch is the right kind of solution but I don't know how to set it up really, it is very confusing.
Because I have seen many examples where Local Notification is used to send reminders at certain dates on the calendar and trigger alarms at certain times and not so much about polling to a server.
How do you set up a Local Notification that will poll to a server at the interval of 10 seconds and then cancel it as soon as it receives right kind of information that it will display at last?
Here is how I have done so far:
...
NSTimer *confirmedTimer;
int orderId = 1;
...
-(IBAction) sendButton: (id) sender {
confirmedTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(confirmedTick) userInfo:nil repeats:YES];
}
-(void)confirmedTick {
NSString *paramsConfirmed = [NSString stringWithFormat:#"order_id=%d", orderId];
NSData *postDataConfirmed = [paramsConfirmed dataUsingEncoding:NSUTF8StringEncoding];
NSURL *urlConfirmed = [NSURL URLWithString:#"http://www.serverexample.com/confirmed.php"];
NSMutableURLRequest *requestConfirmed = [NSMutableURLRequest requestWithURL:urlConfirmed];
[requestConfirmed setHTTPMethod:#"POST"];
[requestConfirmed addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[requestConfirmed setHTTPBody:postDataConfirmed];
[requestConfirmed setValue:[NSString stringWithFormat:#"%i", postDataConfirmed.length] forHTTPHeaderField:#"Content-Length"];
NSURLResponse *responseConfirmed;
NSError *errorConfirmed = nil;
NSData *receivedDataConfirmed = [NSURLConnection sendSynchronousRequest:requestConfirmed
returningResponse:&responseConfirmed
error:&errorConfirmed];
if(errorConfirmed) {
if([responseConfirmed isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponseConfirmed = (NSHTTPURLResponse *)responseConfirmed;
return;
}
return;
}
NSString *responseStringConfirmed = [[NSString alloc] initWithData:receivedDataConfirmed
encoding:NSUTF8StringEncoding];
if ([responseStringConfirmed isEqualToString:#"true"]) {
return;
}
NSDictionary *jsonObjectConfirmed = [responseStringConfirmed objectFromJSONString];
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:receivedDataConfirmed options:0 error:nil];
NSArray *confirmedArray = [jsonDictionary objectForKey:#"confirmed_table"];
if([confirmedArray count] > 0)
{
[confirmedTimer invalidate];
NSString *confirmedMessage = #"";
for(NSDictionary *confirmed in confirmedArray)
{
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"confirmed_id: %#\n", [NSNumber numberWithInt:[[confirmed objectForKey:#"confirmed_id"] intValue]]]];
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"order_id: %#\n", [NSNumber numberWithInt:[[confirmed objectForKey:#"order_id"] intValue]]]];
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"Information: %#", [confirmed objectForKey:#"information"]]];
}
UIAlertView *confirmedAlert = [[UIAlertView alloc]
initWithTitle:#"Confirmation"
message:confirmedMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[confirmedAlert show];
[confirmedAlert release];
}
}
You have it slightly backwards. The local notification doesn't check the server. Rather you implement background fetch and then post a local notification if the background fetch detects the relevant data. There is a good tutorial on background fetch here.
Note that background fetch won't execute every 10 seconds
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.
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.
I have a lot of classes which are sending requests and finally it all comes to SplitViewController. In the SplitUIviewclass I have to long poll and write the data in a table view. The long polling is done in the background thread, so I have declared a variable in app delegate, but when it comes to that it is nil. And the problem is whenever I try to access the NSMutablearray through the appdelegate, its coming as nil and the array is being released. My code for long polling is
- (void) longPoll {
#autoreleasePool
{
//compose the request
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"http://www.example.com/pollUrl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
//pass the response on to the handler (can also check for errors here, if you want)
[self performSelectorOnMainThread:#selector(dataReceived:)
withObject:responseData waitUntilDone:YES];
}
//send the next poll request
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) startPoll {
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) dataReceived: (NSData*) theData {
//i write data received here to app delegate table
}
If I call any other method in my SplitView class from data received, I'm losing control, also I cannot print my app delegate values in data received or the variables being released, I cannot call reload table or any other method from here.
Cant you set your properties in your ViewControllers as strong/retain like so
property (strong,retain) NSMutableArray *myData;
BTW, I learned a moment ago that it is bad practise to use your AppDelegate as a storage place for global containers. The ApplicationDelegate is a place for application delegate methods and for the initial setup of the foundation of your app; such as setting up the navigationController.
So consider storing your data in the appropriate place, perhaps core data or something else.