Perform AFHTTPClient request, in background, with NSOperationQueue chronologically - iphone

I have an HTTPClient request as follows :
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:urlStringMain]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
//parameters
nil];
[self beginBackgroundUpdateTask];
[httpClient postPath:postPath parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
//id results = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONWritingPrettyPrinted error:nil];
//completion code
[self endBackgroundUpdateTask];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure code
}];
[httpClient release];
The background task is executed in :
- (void) beginBackgroundUpdateTask{
[operationQueue addOperationWithBlock:^{
NSLog(#"started upload process as a background job");
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}];
}
And ended in :
- (void) endBackgroundUpdateTask{
NSLog(#"complete upload process as a background job");
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
where self.backgroundUpdateTask is a UIBackgroundTaskIdentifier object, and operationQueue is an object of NSOperationQueue (public member), initialized in viewDidLoad:
operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
Now what I want to do is, perform these requests chronologically, in the background, such that pushing/popping from the viewController does not affect the request. It should also not be affected if the application goes into background. At times I post text, and at other times I post an image. Now, images take longer to upload, than text so if subsequent requests of text and images are made, texts are posted first, and images later. This breaks the chronology of the tasks, hence I wanted to use the NSOperationQueue. But being new to operation queues, I cannot seem to make it work. The chronology is still not being respected. How do I perform the task in the way I want to.
PS. Also, as you can see in the code, i have added [self endBackgroundUpdateTask] in both, the completion block of the httpClient request, and the beginBackgroundUpdateTask method. Now i understand this is not good. Where exactly should the endBackgroundUpdateTask method be called ?
Thank you.

The HTTPClient can handle the background task for you if you ask it to, simply set the appropriate flag (you need to create the operation and call setShouldExecuteAsBackgroundTaskWithExpirationHandler:).
Rather than change how the uploads operate, keep it simple. Send a date with the upload and use that to maintain your order information on the server.
If you really must execute your operations serially then you can get the operationQueue from the client and set it to only execute a single operation at a time.
In either case you shouldn't need to create your own operation queue.

Related

iPhone how to fetch data asynchronously from a web service with API call limit?

I'm pulling data from a web service with an API call limit of 125 per hour. My initial sync of the user's data will use a method similar to the one below. I'm having trouble understanding the correctness of concurrency implications of the code below.
I'm adding a series of AFHTTPRequestOperation over to a serial NSOPerationsQueue (max concurrent count = 1). The resulting calls return asynchronously and cause the method to process the data dictionary. Because of the API call limit, I know that at some point my code will fail and start to return error dictionaries instead.
Can I expect the following code to return full data dictionaries sequentially, or due to asynchronous nature of callbacks, can some of them complete before earlier requests?
Because I'm trying to do initial sync, I want to make sure that once the code fails due to API call limit, I have no "holes" in my data up until the failure point.
-(void)addRequestWithString:(NSString*)requestString
{
// 1: Create a NSURL and a NSURLRequest to points to the web service providing data. Add Oauth1 information to the request, including any extra parameters that are not in scope of Oauth1 protocol
NSURL *url = [NSURL URLWithString:requestString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[ self.auth authorizeRequest:request withExtraParams:self.extraAuthParameters];
// 2: Use AFHTTPRequestOperation class, alloc and init it with the request.
AFHTTPRequestOperation *datasource_download_operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
// 3: Give the user feedback, while downloading the data source by enabling network activity indicator.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// 4: By using setCompletionBlockWithSuccess:failure:, you can add two blocks: one for the case where the operation finishes successfully, and one for the case where it fails.
[datasource_download_operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary* dictonary = [NSJSONSerialization JSONObjectWithData:(NSData *)responseObject
options:NSJSONReadingMutableContainers error:&error];
[self processResponseDictionary:dictonary];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
// 8: In case you are not successful, you display a message to notify the user.
// Connection error message
DLog(#"API fetch error: %#", error);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}];
// 9: Finally, add ìdatasource_download_operationî to ìdownloadQueueî of PendingOperations.
[[self syncQueue] addOperation:datasource_download_operation];
}
Your approach will continue the operations even after they start failing.
If you need the operations to go one at a time, but stop once the failure block is hit, enqueue a new request in the completion block of the prior request.
(This code is from an answer to AFNetworking Synchronous Operation in NSOperationQueue on iPhone; I didn't write it.)
NSEnumerator *enumerator = [operations reverseObjectEnumerator];
AFHTTPRequestOperation *currentOperation = nil;
AFHTTPRequestOperation *nextOperation = [enumerator nextObject];
while (nextOperation != nil && (currentOperation = [enumerator nextObject])) {
currentOperation.completionBlock = ^{
[client enqueueHTTPRequestOperation:nextOperation];
}
nextOperation = currentOperation;
}
[client enqueueHTTPRequestOperation:currentOperation];
If the failure block is hit, the following operations will never be enqueued.

iOS background Location not sending http request

My app needs to track the users location in the background but it is failing to send a 'get' request. The http request gets sent immediately when the app comes to the foreground. I am using RestKit for all my network requests and I followed this tutorial to setup my background locations service.
In my applicationDidEnterBackground
-(void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgLocationManager = [[CLLocationManager alloc] init];
self.bgLocationManager.delegate = self;
[self.bgLocationManager startMonitoringSignificantLocationChanges];
NSLog(#"Entered Background");
}
and I stopMonitoringSignificantLocationChange in my applicationDidBecomeActive delegate
This is my locationManager delegate where I accept the new updated location and send to my server
-(void) locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"I am in the background");
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
// ANY CODE WE PUT HERE IS OUR BACKGROUND TASK
NSString *currentLatitude = [[NSString alloc]
initWithFormat:#"%g",
newLocation.coordinate.latitude];
NSString *currentLongitude = [[NSString alloc]
initWithFormat:#"%g",
newLocation.coordinate.longitude];
NSString *webToken = [[NSUserDefaults standardUserDefaults] stringForKey:#"userWebToken"];
NSLog(#"I am in the bgTask, my lat %#", currentLatitude);
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:webToken, #"auth_token", currentLongitude, #"lng", currentLatitude, #"lat", nil];
RKRequest* request = [[RKClient sharedClient] post:#"/api/locations/background_update" params:queryParams delegate:self];
//default is RKRequestBackgroundPolicyNone
request.backgroundPolicy = RKRequestBackgroundPolicyContinue;
// AFTER ALL THE UPDATES, close the task
if (bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
The network requests works as planned but it will not get called in the background. Is there any additional steps I need? In my info.plist I have the Required Background modes key and location-services as the value.
EDIT
I also referred to this past SO answer. I ran some tests with putting logs throughout the didUpdateToLocation call and they were all called but the 'get' request was not sent. Instead when I finally launch the app to the foreground it sent all the built of network requests (over 10).
EDIT (2)
I added RKRequestBackgroundPolicyContinue to my request but it did not change my results. (As you can see here in the background upload/download for restkit). I see Restkit initialize the host but fails to send the request until the app becomes active.
ANSWER
RestKit must be doing something that is prohibited in the background. Using an NSURLRequest works perfectly.
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.example.com/api/locations/background_update"]];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:jsonData];
NSHTTPURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
It is fine to use a synchronous request since there is no UI to disrupt with background tasks
Re-creating original suggestion as an answer
Have your try replacing your restKit calls with a stock synchronous NSURLConnection? – dklt Sep 20
I'm using exactly the same code as you and it works for me in RestKit. The only way I could make it work is ny creating a synchronous request (it doesn't make a lot of sense to do it asynchronously in this context anyway!). Please check this code and let us know if it works:
// REMEMBER. We are running in the background if this is being executed.
// We can't assume normal network access.
// bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
// Note that the expiration handler block simply ends the task. It is important that we always
// end tasks that we have started.
_bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:_bgTask];
}];
// ANY CODE WE PUT HERE IS OUR BACKGROUND TASK
// For example, I can do a series of SYNCHRONOUS network methods (we're in the background, there is
// no UI to block so synchronous is the correct approach here).
NSNumber *latNumber = [NSNumber numberWithDouble:location.coordinate.latitude];
NSNumber *lngNumber = [NSNumber numberWithDouble:location.coordinate.longitude];
NSNumber *accuracyNumber = [NSNumber numberWithDouble:location.horizontalAccuracy];
NSDictionary *params = [NSDictionary dictionaryWithKeysAndObjects:#"lat",latNumber,#"lng",lngNumber,#"accuracy",accuracyNumber, nil];
RKURL *URL = [RKURL URLWithBaseURL:[NSURL URLWithString:SERVER_URL] resourcePath:#"/user/location/update" queryParameters:params];
RKRequest *request = [RKRequest requestWithURL:URL];
request.method = RKRequestMethodGET;
NSLog(#"Sending location to the server");
RKResponse *response = [request sendSynchronously];
if (response.isFailure)
NSLog(#"Unable to send background location, failure: %#", response.failureErrorDescription);
else {
NSError *error = nil;
NSDictionary *parsedBody = [response parsedBody:&error];
if (YES == [[parsedBody objectForKey:#"result"] boolValue]){
NSLog(#"Background location sent to server");
}
else {
//Something went bad
NSLog(#"Failed to send background location");
}
}
// AFTER ALL THE UPDATES, close the task
if (_bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:_bgTask];
_bgTask = UIBackgroundTaskInvalid;
}
I'm almost sure the new thread spawned for your RKClient request is automatically killed after invoking it.
When you're application is running in the background you can finish a HTTP request you started before you entered the background but you cannot initiate a new request. You can only initiate certain network operations while in the background (voip, newsstand).

How do you remove a UIButton/keyboard/UIAlertView from the view while json or other data is being loaded?

I'm using a UISearchBar in my application and the problem is when I call a few json methods searchBarSearchButtonClicked seems to not resign the keyboard until the other methods are done loading the data. I've tried alternatively using UIAlertView and UIButtons to replace the searchBarSearchButtonClicked function but they appear to literally freeze and stay in a "pressed down" state too. I was also wondering if this would be a reason why [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; wouldn't show an activity indicator in the device's status bar.
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
self.args = searchBar.text;
[self grabData];
[self fillVars];
[searchBar resignFirstResponder];
[self.tableView reloadData];
}
[self grabData] is where I grab the JSON data and [self fillVars] just fills a few things that are later used.
-(void)grabData{
self.args = [self.args stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
urlString = [NSString stringWithFormat:#"%#%#?key=%#&q=%#",baseUrl,func,apiKey,args];
url = [NSURL URLWithString:urlString];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
self.matches = [json objectForKey:#"matches"];
[UIApplication sharedApplication].networkActivityIndicatorVisible=YES;
}
You will have to use threading. All manipulation with your interface happens on the main thread, so when you perform a lengthy task on the main thread, the interface won't be able to update itself before the task has completed.
In a UIViewController you can do [self performSelectorInBackground:#selector(grabData) withObject:self], which is a convenience method for dispatching a new queue (thread) using grand central dispact.
You could also do that manually, using the GCD API. You would do something along the lines of this:
dispatch_queue_t jsonQueue = dispatch_queue_create("JSON Queue", NULL);
dispatch_async(jsonQueue, ^{
// fetch JSON data ...
dispatch_async(dispatch_get_main_queue(), ^{
// perhaps do something back on the main queue once you're done!
});
});

NSInvocationOperation ignoring maxConcurrentOperationCount

I'm trying to queue up some TWRequest calls using NSInvocationOperation. It seems to add the method calls in the correct order, but the doSomething: methods get called at the same time pretty much, ie run concurrently, rather than one after the other, which is what I want to achieve.
In addition, the complete in the wrong order, which suggests it's not running one after the other...
- (void)prepare {
if(!self.queue){
self.queue = [[NSOperationQueue alloc] init];
[self.queue setMaxConcurrentOperationCount:1];
}
for(NSString *text in calls){
NSLog(#"Adding to Queue... %#", text);
NSInvocationOperation *indexOperation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(doSomething:) object:text];
[self.queue addOperation:indexOperation];
}
}
- (void)doSomething:(NSString*)someText {
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://something.com"] parameters:nil requestMethod:TWRequestMethodGET];
NSLog(#"About to Perform Request... %#", someText);
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
dispatch_sync(dispatch_get_main_queue(), ^{
// works fine
NSLog(#"Network Finished... %#", someText);
});
}];
}
In the log I see this:
2011-12-30 18:34:34.553 app[32745:10703] Adding to Queue... 1
2011-12-30 18:34:34.555 app[32745:10703] Adding to Queue... 2
2011-12-30 18:34:34.556 app[32745:10703] Adding to Queue... 3
2011-12-30 18:34:34.557 app[32745:13e03] About to Perform Request... 1
2011-12-30 18:34:34.560 app[32745:13e03] About to Perform Request... 2
2011-12-30 18:34:34.563 app[32745:13e03] About to Perform Request... 3
2011-12-30 18:34:35.303 app[32745:10703] Network finished... 3
2011-12-30 18:34:35.454 app[32745:10703] Network finished... 2
2011-12-30 18:34:35.601 app[32745:10703] Network finished... 1
I'm expecting to see (2) to Perform Request after (1) has finished etc... Any pointers?
The operation queue is working fine. As #Joe said in the comment, performRequestWithHandler: starts an asynchronous connection and returns immediately. You can see this by adding an NSLog to the end of doSomething as follows:
- (void)doSomething:(NSString*)someText {
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://something.com"] parameters:nil requestMethod:TWRequestMethodGET];
NSLog(#"About to Perform Request... %#", someText);
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
dispatch_sync(dispatch_get_main_queue(), ^{
// works fine
NSLog(#"Network Finished... %#", someText);
});
}];
NSLog(#"doSomething Finished");
}
To have each request happen serially you need to either make the request synchronous within the operation (use the signedRequest method and a synchronous NSURLConnection) or don't use an operation queue and invoke the next request in the completion handler of the current request. Keep in mind that if you use an operation queue the order in which operations are performed is not based on the order they are added. You might consider using GCD directly with a serial dispatch queue.

How can I check the progress of my Facebook iOS upload?

I'm using the Facebook iOS SDK and using the Graph API to upload videos to Facebook.
The uploading is working perfectly fine, but can I keep track of the progress of the upload so I can reflect the progress in a progress bar.
This is an old question but what you're trying to do is possible with latest Facebook iOS SDK v3.9. (27 Oct 2013)
Essentially, FBRequestConnection exposes a property urlRequest (NSMutableURLRequest) that you can use to send out the data any other third party networking frameworks or even the ones Apple provided.
https://developers.facebook.com/docs/reference/ios/current/class/FBRequestConnection#urlRequest
Here's an example how I get progress callbacks using AFNetworking 1.x.
Prepare Request Body
NSDictionary *parameters = #{ #"video.mov": videoData,
#"title": #"Upload Title",
#"description": #"Upload Description" };
Create FBRequest
FBRequest *request = [FBRequest requestWithGraphPath:#"me/videos"
parameters:parameters
HTTPMethod:#"POST"];
Generate FBRequestConnection (Cancel & Extract URLRequest)
FBRequestConnection *requestConnection = [request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
}];
[requestConnection cancel];
NSMutableURLRequest *urlRequest = requestConnection.urlRequest;
Use AFNetworking HTTPRequestOperation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Do your success callback.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Do your failure callback.
}];
Set Progress Callback
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
Start the operation
[[APIClient sharedInstance] enqueueHTTPRequestOperation:operation];
// APIClient is a singleton class for AFHTTPClient subclass
I've finally found a way of doing this after looking around in NSURLConnection. It means adding the following code inside of the FBRequest.h and FBRequest.m files to create a new delegate.
At the bottom of the FBRequest.m file there are all of the methods for NSURLConnectionDelegate. Add this code here:
- (void)connection:connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
float percentComplete = ((float)totalBytesWritten/(float)totalBytesExpectedToWrite);
if ([_delegate respondsToSelector:#selector(request:uploadPercentComplete:)])
{
[_delegate request:self uploadPercentComplete:percentComplete];
}
}
Now put this in the FBRequest.h class to create a new FBRequest delegate:
/**
* Called a data packet is sent
*
* The result object is a float of the percent of data sent
*/
- (void)request:(FBRequest *)request uploadPercentComplete:(float)per;
This goes at the bottom of the FBRequest.h file after:
#protocol FBRequestDelegate <NSObject>
#optional
Now all you have to do is call this new delegate anywhere in your code like you would any other FBRequest delegate and it will give you a float from 0.0 to 1.0 (0% to 100%).
Strange that the Facebook API doesn't have this (along with upload cancel which I found out how to do here How to cancel a video upload in progress using the Facebook iOS SDK?) as it's not that tricky.
Enjoy!