iOS 7 background upload and POST requests - ios

I need to upload files in the background with other HTTP POST requests before and after each upload.
Previously I was using beginBackgroundTaskWithExpirationHandler which was working perfectly till iOS 6 but from IOS 7 it is restricted for approx 180 seconds only which is a concern.
I have read the documents regarding NSURLSession were in we have Background transfer service. But the problem with this is it only allows upload and download in background. It doesn't allow me to make POST request after every upload in the background.
So is there any way to make the POST request along with the background uploads?
Any hint in the right direction would be highly appreciated.

I think you can use NSURLSessionDownloadTask to send a POST.
IMO, download task doesn't mean it is used for download. it means the response of your POST request (json/xml) will be downloaded to a local file. Then you can open that file and parse it to get the request.
if you want you can even use a NSURLSessionDownloadTask to upload files to S3. and the s3 response will be 'downloaded' to a local file..
for more information, see this question in the apple developer forum https://devforums.apple.com/thread/210515?tstart=0

I successfully round-trip a vanilla http call during a background task's callback, in production code. The callback is:
-[id<NSURLSessionTaskDelegate> URLSession:task:didCompleteWithError:]
You get about 30 seconds there to do what you need to do. Any async calls made during that time must be bracketed by
-[UIApplication beginBackgroundTaskWithName:expirationHandler:]
and the "end task" version of that. Otherwise, iOS will kill your process when you pop the stack (while you are waiting for your async process).
BTW, don't confuse UIApplication tasks (I call them "app tasks") and NSURLSession tasks ("session tasks").

If you use uploadTaskWithRequest:fromData:completionHandler: you can make your HTTP POST request from the completion handler block:
[backgroundSession uploadTaskWithRequest:request fromData:data completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestForURL:[NSURL URLWithString:#"http://somethingorother.com/"]];
request.HTTPMethod = #"POST";
.
.
.
NSURLResponse *postResponse;
NSError *postError;
NSData *postResponseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&postResponse error:&postError];
// Check postResponse and postError to ensure that the POST succeeded
}
}];

Related

NSURLSessionTask Never Calls Back When WiFi Off

When I turn off my WiFi connection and run the following code on the iPhone 6s 10.2 simulator, the callback is never executed. I expected the callback to fire fairly quickly with an error like "No Internet connection".
NSLog(#"request-start");
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://www.google.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:0];
task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"request-end");
}];
[task resume];
My Problem
I'm fetching data when the app first loads. If wifi is turned off, I need to show an error. If I set a timeout, it is obeyed - but it would need to be 10+ seconds and I'd rather not make them wait. I've also tried to detect the network status with reachability, but the network status is often unknown when the app is first loaded.
timeoutIntervalForResource
This property determines the resource timeout interval for all tasks
within sessions based on this configuration. The resource timeout
interval controls how long (in seconds) to wait for an entire resource
to transfer before giving up. The resource timer starts when the
request is initiated and counts until either the request completes or
this timeout interval is reached, whichever comes first.
The default value is 7 days.
and
timeoutIntervalForRequest
Important
Any upload or download tasks created by a background session
are automatically retried if the original request fails due to a
timeout. To configure how long an upload or download task should be
allowed to be retried or transferred, use the
timeoutIntervalForResource property.
The default value is 60.
So, without your timeout set, your connection will run for 7 days.
In general an NSURLSession background session does not fail a task if
something goes wrong on the wire. Rather, it continues looking for a
good time to run the request and retries at that time. This continues
until the resource timeout expires (that is, the value in the
timeoutIntervalForResource property in the NSURLSessionConfiguration
object you use to create the session). The current default for that
value is one week!
Source

Cannot get background URL Session to return

I have tried and failed many times to get some inherited code to successfully send a background session request. When in the foreground, it works flawlessly, but in the background it will either never return, or, in some cases, receive a auth challenge.
The code to create the request is pretty boilerplate:
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
sessionConfig.sessionSendsLaunchEvents = true;
sessionConfig.discretionary = false;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfig.timeoutIntervalForResource = 60 * 60 * 24; //One day. Default is 7 days!
/* Create session, and optionally set a NSURLSessionDelegate. */
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSURLComponents *urlComponents = [NSURLComponents new];
urlComponents.scheme = #"https";
urlComponents.host = #"jsonplaceholder.typicode.com";
urlComponents.path = #"/posts/1";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[urlComponents URL]];
request.HTTPMethod = #"PUT";
NSLog(#"Making request: %#", request);
/* Start a new Task */
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
But I never get anything back. I do have the required background modes (probably)
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
and my delegate is delegating everything
<NSURLSessionDelegate, NSURLSessionDataDelegate,NSURLConnectionDataDelegate,NSURLConnectionDelegate,NSURLSessionTaskDelegate>
Any help would be appreciated- I'm pretty sure I'm missing just one line of code somewhere. I even went so far as to create a GitHub repo just to test this code out- it schedules a Local Notification to allow you to run the session task in the background.
GitHub repo for BackgroundSender
In the app delegate's handleEventsForBackgroundURLSession, you are doing nothing with the completion handler. You should:
save copy of the completionHandler;
start the background NSURLSession with that identifier; and
when done handling all of the delegate methods, call the saved completion handler; we often do that in URLSessionDidFinishEventsForBackgroundURLSession.
By the way, you shouldn't use data tasks with background sessions. You generally should use a download or upload task.
Also, those background modes are not necessary for background NSURLSession. Background fetch is designed for a different problem, namely periodically polling to see if your server has data available. If you need that, then by all means, use the fetch background mode, but realize that this is a separate topic from merely performing background requests.
See Fetching Small Amounts of Content Opportunistically in the App Programming Guide for iOS: Background Execution for a discussion of background fetch, and compare that to the Downloading Content in the Background section.
Likewise, the remote-notification key is unrelated, too. As the docs say, remote-notification is used when "The app wants to start downloading content when a push notification arrives. Use this notification to minimize the delay in showing content related to the push notification."

NSURLSession: Synchronous task hangs

I have recently replaced NSURLConnection to NSURLSession in my code.
As I am using many synchronous url-requests and NSURLSession doesn't support one, I used semaphores to make NSURLSessionDataTask synchronous.
I referred this link: https://forums.developer.apple.com/thread/11519
I have a singleton 'Network Manger' with NSURLSession as a member variable. NSURLSession is instantiated only once and tasks are added to it.
But now synchronous request cause performance issues in my app. There are lags and app hangs when synchronous request is sent.
Her is the call to synchronous request:
NSURLResponse *urlResponse;
NSError *error;
id serverResponse=[[MyNetworkManager sharedInstance] synchronousDataTaskWithRequest:request
returningResponse:&urlResponse
error:&error];
Everything works perfectly if I do not make NetworkManager as singleton and instantiate it everytime.
MyNetworkManager *manger=[[MyNetworkManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] responseSerializer:nil];
NSURLResponse *urlResponse;
NSError *error;
id *serverResponse=[manger synchronousDataTaskWithRequest:request returningResponse:&urlResponse error:&error];
Notes:
1.Exact Steps: There are total 3 (2 async and 1 sync ) NSURLSessionDataTasks. Second async task is called in completion handler of first task. Third synchronous task is called from completion handler of second. This third task is blocked forever and code doesn't proceed.
2.Everything is happening on background thread.
Why does synchronous data task work only if it is added to a new NSURLSession? Why it doesn't work if added to NSURLSession which has already executed two data tasks?
I was facing the similar issue when I used to synchronus implementation using Semaphore and here is the cause of that issue :
I think that your an HTTP request is causing a redirection, and in the willPerformHTTPRedirection method, you haven't called the completionHandler for the new request. Try implementing the method as :
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSLog(#"willPerformRedirection");
completionHandler(request);
}

No Response during web service call

I want to do a web service call on a separate thread. I am using the below code,
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// My web service request goes from here
});
The request goes successfully, however there is no response coming back, I am not able to figure out why.
If I use
dispatch_async(dispatch_get_main_queue(), ^{
// My Web service request goes from here
});
Response is coming back but UI is blocked till then, I want to do the web service request in a separate thread without blocking the UI.
The idea is to use a background thread for the request, but handle the result (which changes UI) on the main. This answer shows how (basically by nesting your main queue block inside your background block), but there's a simpler way provided by NSURLConnection:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http..."]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// update the UI here
}];
This handy method performs the request off the main, and -- since you pass it the main queue -- performs the completion block on the main.

What is best option for read only database for iPhone app?

I want to make some information app where user can ready only data.DB contains text only paragraph + 1 picture in each row. My question is, what is good way of doing it? I will need to add more data to DB with more information in future.
Let say if my database size is 30-40MB, is it good to put all together in app? Like preload SQlite and attach that to app.
I would like to know if I create my own server, is it worth it because it's not big Database and no need to save user data either.
30-40MB doesn't hit the limit for iOS OTA downloads but it is a bit bulky. If it is a static DB though have you considered just putting it on S3? Try the following:
Create a JSON file with the data structured the way you would like and put it on a public S3 link. For the images use URLs that are also hosted on S3 (or whatever file server you want to use to serve this...I only say S3 because it is pretty ubiquitous).
When the app launches go and grab the JSON file like so:
NSURL *pathToJSON = [NSURL URLWithString:#"http://someurl..."];
NSURLRequest *request = [NSURLRequest requestWithURL:pathToJSON];
[NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSError *error;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
}];
You can then choose to either download and cache the images locally right away or just fetch them as needed. This way you get all the benefits of server updating without needing to maintain a true API server.

Resources