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
}
Related
-(instancetype)initWithDataTaskWithUrlRequest:(NSURLRequest *)request WithIdentifierIndex:(NSString*)identifier
{
self = [super init];
if (self) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:#"%#",[self uuid]]];
configuration.HTTPMaximumConnectionsPerHost = 1;
operationSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];
_sessionDataTask = [operationSession dataTaskWithRequest:request];
}
return self;
}
I have a operation queue. And In each NSOperation i have used session to downloading the content. I have ephemeralSessionConfiguration and DefaultSessionConfiguration. It is working fine for 200 0r 300 operations. But when the operation increase to 1000 to 1000+ it will give me time out, handshaking error. Please help me for best solution.
In the same NSURLSessionConfiguration you can add/Increase timeout as :
configuration.timeoutIntervalForRequest = KTimeOutValue; //Your timeout value
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.
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
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()
}
I'm a bit confuse of how to take advantage of the new iOS 7 NSURLSession background transfers features and AFNetworking (versions 2 and 3).
I saw the WWDC 705 - What’s New in Foundation Networking session, and they demonstrated background download that continues after the app terminated or even crashes.
This is done using the new API application:handleEventsForBackgroundURLSession:completionHandler: and the fact that the session's delegate will eventually get the callbacks and can complete its task.
So I'm wondering how to use it with AFNetworking (if possible) to continue downloading in background.
The problem is, AFNetworking conveniently uses block based API to do all the requests, but if the app terminated or crashes those block are also gone. So how can I complete the task?
Or maybe I'm missing something here...
Let me explain what I mean:
For example my app is a photo messaging app, lets say that I have a PhotoMessage object that represent one message and this object has properties like
state - describe the state of the photo download.
resourcePath - the path to the final downloaded photo file.
So when I get a new message from the server, I create a new PhotoMessage object, and start downloading its photo resource.
PhotoMessage *newPhotoMsg = [[PhotoMessage alloc] initWithInfoFromServer:info];
newPhotoMsg.state = kStateDownloading;
self.photoDownloadTask = [[BGSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *filePath = // some file url
return filePath;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error) {
// update the PhotoMessage Object
newPhotoMsg.state = kStateDownloadFinished;
newPhotoMsg.resourcePath = filePath;
}
}];
[self.photoDownloadTask resume];
As you can see, I use the completion block to update that PhotoMessage object according to the response I get.
How can I accomplish that with a background transfer? This completion block won't be called and as a result, I can't update the newPhotoMsg.
A couple of thoughts:
You have to make sure you do the necessary coding outlined in the Handling iOS Background Activity section of the URL Loading System Programming Guide says:
If you are using NSURLSession in iOS, your app is automatically relaunched when a download completes. Your app’s application:handleEventsForBackgroundURLSession:completionHandler: app delegate method is responsible for recreating the appropriate session, storing a completion handler, and calling that handler when the session calls your session delegate’s URLSessionDidFinishEventsForBackgroundURLSession: method.
That guide shows some examples of what you can do. Frankly, I think the code samples discussed in the latter part of the WWDC 2013 video What’s New in Foundation Networking are even more clear.
The basic implementation of AFURLSessionManager will work in conjunction with background sessions if the app is merely suspended (you'll see your blocks called when the network tasks are done, assuming you've done the above). But as you guessed, any task-specific block parameters that are passed to the AFURLSessionManager method where you create the NSURLSessionTask for uploads and downloads are lost "if the app terminated or crashes."
For background uploads, this is an annoyance (as your task-level informational progress and completion blocks you specified when creating the task will not get called). But if you employ the session-level renditions (e.g. setTaskDidCompleteBlock and setTaskDidSendBodyDataBlock), that will get called properly (assuming you always set these blocks when you re-instantiate the session manager).
As it turns out, this issue of losing the blocks is actually more problematic for background downloads, but the solution there is very similar (do not use task-based block parameters, but rather use session-based blocks, such as setDownloadTaskDidFinishDownloadingBlock).
An alternative, you could stick with default (non-background) NSURLSession, but make sure your app requests a little time to finish the upload if the user leaves the app while the task is in progress. For example, before you create your NSURLSessionTask, you can create a UIBackgroundTaskIdentifier:
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
But make sure that the completion block of the network task correctly informs iOS that it is complete:
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
This is not as powerful as a background NSURLSession (e.g., you have a limited amount of time available), but in some cases this can be useful.
Update:
I thought I'd add a practical example of how to do background downloads using AFNetworking.
First define your background manager.
//
// BackgroundSessionManager.h
//
// Created by Robert Ryan on 10/11/14.
// Copyright (c) 2014 Robert Ryan. All rights reserved.
//
#import "AFHTTPSessionManager.h"
#interface BackgroundSessionManager : AFHTTPSessionManager
+ (instancetype)sharedManager;
#property (nonatomic, copy) void (^savedCompletionHandler)(void);
#end
and
//
// BackgroundSessionManager.m
//
// Created by Robert Ryan on 10/11/14.
// Copyright (c) 2014 Robert Ryan. All rights reserved.
//
#import "BackgroundSessionManager.h"
static NSString * const kBackgroundSessionIdentifier = #"com.domain.backgroundsession";
#implementation BackgroundSessionManager
+ (instancetype)sharedManager {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (instancetype)init {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBackgroundSessionIdentifier];
self = [super initWithSessionConfiguration:configuration];
if (self) {
[self configureDownloadFinished]; // when download done, save file
[self configureBackgroundSessionFinished]; // when entire background session done, call completion handler
[self configureAuthentication]; // my server uses authentication, so let's handle that; if you don't use authentication challenges, you can remove this
}
return self;
}
- (void)configureDownloadFinished {
// just save the downloaded file to documents folder using filename from URL
[self setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)downloadTask.response statusCode];
if (statusCode != 200) {
// handle error here, e.g.
NSLog(#"%# failed (statusCode = %ld)", [downloadTask.originalRequest.URL lastPathComponent], statusCode);
return nil;
}
}
NSString *filename = [downloadTask.originalRequest.URL lastPathComponent];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:filename];
return [NSURL fileURLWithPath:path];
}];
[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
if (error) {
// handle error here, e.g.,
NSLog(#"%#: %#", [task.originalRequest.URL lastPathComponent], error);
}
}];
}
- (void)configureBackgroundSessionFinished {
typeof(self) __weak weakSelf = self;
[self setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) {
if (weakSelf.savedCompletionHandler) {
weakSelf.savedCompletionHandler();
weakSelf.savedCompletionHandler = nil;
}
}];
}
- (void)configureAuthentication {
NSURLCredential *myCredential = [NSURLCredential credentialWithUser:#"userid" password:#"password" persistence:NSURLCredentialPersistenceForSession];
[self setTaskDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
if (challenge.previousFailureCount == 0) {
*credential = myCredential;
return NSURLSessionAuthChallengeUseCredential;
} else {
return NSURLSessionAuthChallengePerformDefaultHandling;
}
}];
}
#end
Make sure app delegate saves completion handler (instantiating the background session as necessary):
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
NSAssert([[BackgroundSessionManager sharedManager].session.configuration.identifier isEqualToString:identifier], #"Identifiers didn't match");
[BackgroundSessionManager sharedManager].savedCompletionHandler = completionHandler;
}
Then start your downloads:
for (NSString *filename in filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[[BackgroundSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil] resume];
}
Note, I don't supply any of those task related blocks, because those aren't reliable with background sessions. (Background downloads proceed even after the app is terminated and these blocks have long disappeared.) One must rely upon the session-level, easily recreated setDownloadTaskDidFinishDownloadingBlock only.
Clearly this is a simple example (only one background session object; just saving files to the docs folder using last component of URL as the filename; etc.), but hopefully it illustrates the pattern.
It shouldn't make any difference whether or not the callbacks are blocks or not. When you instantiate an AFURLSessionManager, make sure to instantiate it with NSURLSessionConfiguration backgroundSessionConfiguration:. Also, make sure to call the manager's setDidFinishEventsForBackgroundURLSessionBlock with your callback block - this is where you should write the code typically defined in NSURLSessionDelegate's method:
URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session. This code should invoke your app delegate's background download completion handler.
One word of advice regarding background download tasks - even when running in the foreground, their timeouts are ignored, meaning you could get "stuck" on a download that's not responding. This is not documented anywhere and drove me crazy for some time. The first suspect was AFNetworking but even after calling NSURLSession directly, the behaviour remained the same.
Good luck!
AFURLSessionManager
AFURLSessionManager creates and manages an NSURLSession object based on a specified NSURLSessionConfiguration object, which conforms to <NSURLSessionTaskDelegate>, <NSURLSessionDataDelegate>, <NSURLSessionDownloadDelegate>, and <NSURLSessionDelegate>.
link to documentation here documentation