iOS NSURLSession Listen to Timeout - ios

I created a simple NSURLSessionDownloadTask to download from a URL, with its class having the NSURLSession delegates:
#interface DownloadManager : NSObject <NSURLSessionDataDelegate, NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate>
//...
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setTimeoutIntervalForRequest:30.0];
[sessionConfiguration setTimeoutIntervalForResource:60.0];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:self.url];
[downloadTask resume];
However, I could not find a protocol method that listens to the download task timing out. Is there a way to listen to the timeout (ex. - I wanted to close a progress dialog box when 30.0 seconds have passed and no data is still received)
I've already scavenged Google but haven't found any information so far, so I'll leave this question here while I search for more info.
Thanks so much!

The timeout is one of the errors NSURLSession will give you in completionHandler block. It's NSURLErrorTimedOut = -1001.
in delegate method
- URLSession:task:didCompleteWithError:
check the NSError if it's NSURLErrorTimedOut do what you want
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/#//apple_ref/doc/constant_group/URL_Loading_System_Error_Codes

Related

Can NSURLSessionDownloadTask resume called twice?

I am new to NSURLSession and i did not find the answer in other stackoverflow question. So i am posting this.
I am having a Button and ProgressBar in my ViewController. Using NSURLSessionDownloadTask's instance, i am calling resume as follows
#property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
Specified above line in #interface
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
self.downloadTask = [session downloadTaskWithURL:url];
Specified above lines in #implementation and called resume method on buttonclick as follows
-(void) buttonpressed:(id)sender{
[self.downloadTask resume];
}
Here what happens is,
When i click the button for first time, it downloads perfectly
(ie. Calling the proper delegate methods
downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite & didFinishDownloadingToURL
But when i click the button again, it's not downloading (ie. Delegate methods are not calling)
1) Where i am doing mistake?
2) I want it to download again if i click the button second time. What
should i do for that?
Any help appreciated, thanks for the time (:
resume is only for suspended tasks, and yours is completed. The simple fix is to create and begin (really, resume) the task in the same function.
- (void)setupAndStartDownload {
// your setup code, from the OP
// then start it here
[self.downloadTask resume];
}
-(void) buttonpressed:(id)sender{
[self setupAndStartDownload];
}

URLSession didCompleteWithError nil error

Working on an IOS9 app that is doing a background URLSession in a controller that is a NSURLSessionDelegate. Here is how I start it:
self.session_data = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:src];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: #"myBackgroundSessionIdentifier"];
self.session = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
self.download = [self.session dataTaskWithRequest: request ];
[self.download resume];
So far so good. I implement the three delegate methods. 'didReceiveData' is called first and I store the data.
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data{
NSLog(#"%s",__func__);
[self.session_data appendData:data];
}
Right after that 'didCompleteWithError' is called. The 'completionHandler' handler is never called.
What is confusing about 'didCompleteWithError' message is that the actual error object is nil. I have seen some similar unanswered questions. I am not leaving the controller/view while loading. Do I need to move that functionality into AppDelegate?
Apple doc said that didCompleteWithError report only client side error, otherwise is nil:
"Server errors are not reported through the error parameter. The only errors your delegate receives through the error parameter are client-side errors, such as being unable to resolve the hostname or connect to the host."
This is the link to the documentation.
If you want to check other errors like session's errors you have to implement session protocol delegate
- URLSession:didBecomeInvalidWithError:
For more details, see this answer

NSURLSessionTask creation fail in extension with backgroundSessionConfiguration

Context:
I try to call a create a task (download or upload) from an action extension, with a backgroundSessionConfiguration.
To do this I fallow the exemple in apple documention
-(void)downloadTest
{
NSURLSession *mySession = [self configureMySession];
NSURL *url = [NSURL URLWithString:#"http://www.sellcell.com/blog/wp-content/uploads/2014/03/dog-apps.jpg"];
NSURLSessionTask *myTask = [mySession downloadTaskWithURL:url];
[myTask resume];
}
- (NSURLSession *) configureMySession {
if (!_mySession) {
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"com.mycompany.myapp.backgroundsession"];
// To access the shared container you set up, use the sharedContainerIdentifier property on your configuration object.
config.sharedContainerIdentifier = #"group.com.mycompany.appname";
_mySession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _mySession;
}
My problem is that when I call [mySession downloadTaskWithURL:url]; it returns nil.
If I change the configuration to NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; then a task is created.
I don't see what I'm doing wrong , I have created an app group and I use it the app and in the extension.
I use the group name that I have created in config.sharedContainerIdentifier but I'm not sure it's necessary.
NOTE: I have the same problem with uploadTask.
Are you using ARC? If not, make sure your session is being retained properly. (It looks like you're using an ivar directly.)
Is that shared container identifier correct? If the container isn't in your entitlements or doesn't exist yet, your session will be immediately invalidated. Add an invalidation delegate method and see if it is getting called. If so, that's probably the issue.

Can I use HTTP caching with an NSURLSessionDownloadTask on iOS?

I'm trying to use NSURLSessionDownloadTask, and take advantage of Apple's in-built URL caching functionality. I have succeeded in getting the caching to work when using an NSURLSessionDataTask using the code below:
- (void)downloadUsingNSURLSessionDataTask:(NSURL *)url {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
- (void)cachedDataTaskTest {
// This call performs an HTTP request
[self downloadUsingNSURLSessionDataTask:[NSURL URLWithString:#"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
[NSThread sleepForTimeInterval:1];
// This call returns the locally cached copy, and no HTTP request occurs
[self downloadUsingNSURLSessionDataTask:[NSURL URLWithString:#"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
}
However, I need to perform a background download for which I have to use an NSURLDownloadTask. When I switch to this the caching behaviour does not occur.
- (void)downloadUsingNSURLSessionDownloadTask:(NSURL *)url {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
[downloadTask resume];
}
- (void)cachedDownloadTaskTest {
// This call performs an HTTP request
[self downloadUsingNSURLSessionDownloadTask:[NSURL URLWithString:#"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
[NSThread sleepForTimeInterval:1];
// This call also performs an HTTP request
[self downloadUsingNSURLSessionDownloadTask:[NSURL URLWithString:#"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
}
This documentation from Apple indicates that NSURLDownloadTasks don't call the URLSession:dataTask:willCacheResponse:completionHandler: delegate method, so it is not possible for your app to hook into the caching life cycle. My guess is that this implies that caching is simply not available for these tasks, but it is not explicit about this.
For a data task, the NSURLSession object calls the delegate’s URLSession:dataTask:willCacheResponse:completionHandler: method. Your
app should then decide whether to allow caching. If you do not
implement this method, the default behavior is to use the caching
policy specified in the session’s configuration object.
Can anyone confirm this hunch that NSURLSessionDownloadTasks simply don't support caching? Is it possible to take advantage of Apple's HTTP caching behaviour in a background task?
NSURLSessionDownloadTask performs work using a system service (daemon) that performs the download outside your application process. Because of this, the delegate callbacks that actually get invoked for a download task are more limited than those for NSURLSessionDataTask. As documented in Life Cycle of a URL Session, a data task delegate will receive callbacks to customize caching behavior, while a download task delegate will not.
A download task should use the caching policy specified by the NSURLRequest, and should use the cache storage specified by the NSURLSessionConfiguration (if it does not, file a bug). The default cache policy is NSURLRequestUseProtocolCachePolicy, and the default URL cache storage is the shared URL cache for non-background and non-ephemeral configurations. The delegate callbacks for URLSession:dataTask:willCacheResponse:completionHandler: are not a good indicator of wether caching is actually occurring.
If you create an NSURLSessionDownloadTask using the default session configuration and do not customize the cache policy of NSURLRequests, caching is already happening.
It looks like NSURLSessionDownloadTask does not cache, by design.
NSURLSessionConfiguration documentation
The defaultSessionConfiguration method is documented:
The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain.
However, none of the other constructors are documented to exclude the above italicized exception. I also tested backgroundSessionConfigurationWithIdentifier and it doesn't appear to do the job either.
Also, requestCachePolicy doesn't offer any way out of the exception either.
NSURLSessionDownloadTask runtime efficiency
NSURLSessionDownloadTask writes incoming data to a temporary file. When it completes the file, it notifies the delegate or completion handler. Finally it deletes the file.
While it could simply move the file into cache at the end, it would have to deal with either the delegate or completion handler actually modifying the file and thus changing its cached representation, or even moving the file to a permanent location it can't track.
It could copy the file before notifying the delegate or completion handler, but this would be inefficient for large files.
It could keep the file read-only, but doesn't appear to do so on iOS 8.0.
Therefore, it's unlikely that the system would do any caching of download tasks.
Workaround
Your best bet is use NSURLSessionDataTask, then when your delegate's URLSession:dataTask:didReceiveData: method is called, append the incoming data to your own file. The next time you use NSURLSessionDataTask you get the cached data all in one call of URLSession:dataTask:didReceiveData:.
Got it working only by storing the response forcefully
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let uRLCache = URLCache(memoryCapacity: 500 * 1024 * 1024, diskCapacity: 500 * 1024 * 1024, diskPath: nil)
URLCache.shared = uRLCache
}
func download() {
let req = URLRequest(url: remoteurl, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 30)
if let cachedResponse = URLCache.shared.cachedResponse(for: req) {
print("found")
}
let downloadTask = URLSession.shared.downloadTask(with: req, completionHandler: { [weak self] (url, res, error) -> Void in
guard let `self` = self else { return }
if let data = try? Data(contentsOf: url), res != nil && URLCache.shared.cachedResponse(for: req) == nil {
// store the response
URLCache.shared.storeCachedResponse(CachedURLResponse(response: res!, data: data), for: req)
}
})
downloadTask?.resume()
}

How to know if NSURLSessionConfiguration is a backgroundSessionConfiguration?

If the user provides it's own NSURLSessionConfiguration, how do I know if I can ask for a NSURLSessionDownloadTask or NSURLSessionDataTask since a NSURLSessionDataTask can't be created for a background session
You can decide weather the provided NSURLSessionConfiguration object is a background session or not by using its identifier property as
NSURLSessionConfiguration *config = inConfig;
if(config.identifier != nil) {
//Background session configuration
}
else {
// not a Background session configuration
}

Resources