5

I am learning NSURLSession to make a custom network class and stumbled upon a very unusual error.

My aim is simple, I want to write the response of a URL in a file, once completed. So I created a download task and assigned it to a defaultSessionConfiguration. In both cases of assigning delegate to the configuration and not assigning delegate to configuration (in which case the completion handler works) works.

Now I shifted to backgroundSessionConfigurationWithIdentifier. Background sessions don't support blocks , so delegate call is mandatory.

Every time it ends up in error . The error is given below

Printing description of error:
Error Domain=NSURLErrorDomain Code=-1 "unknown error" UserInfo={NSErrorFailingURLKey=http://__________________, NSErrorFailingURLStringKey=http://__________________, NSLocalizedDescription=unknown error}

I thought I must have written the background Configuration wrong , so I tested it by creating a demo download task to download an image and adding this task to this background session. It works this time

The code that works is given below :

+ (CustomNetwork *)sharedNetworkObject
{
     if(!netWorkObj)
     {
          netWorkObj = [[CustomNetwork alloc] init];

          //[netWorkObj prepareDataSession];

          //[netWorkObj prepareBackgroundSession];
     }
     return netWorkObj;
}
//
+ (NSOperationQueue *)responseQueue
{
     if(!queue)
     {
          queue = [[NSOperationQueue alloc] init];
     }
     return queue;
}

- (void)prepareDataSession
{
     if(!dataSession)
     {
          NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
          configuration.HTTPMaximumConnectionsPerHost = 5; // This means 1 session can hold to 5 connections
          configuration.timeoutIntervalForRequest = CONNECTION_TIME_OUT;
          configuration.timeoutIntervalForResource = CONNECTION_TIME_OUT;
          dataSession = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:nil //[CustomNetwork sharedNetworkObject]
                                                 delegateQueue:nil];
     }
}

- (void)prepareBackgroundSession
{
     if(!backgroundSession)
     {
          NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundSession"];
          configuration.HTTPMaximumConnectionsPerHost = 5; // This means 1 session can hold to 5 connections
          configuration.timeoutIntervalForRequest = CONNECTION_TIME_OUT;
          configuration.timeoutIntervalForResource = CONNECTION_TIME_OUT;
          configuration.discretionary = NO;// For optimizing
          backgroundSession = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:[CustomNetwork sharedNetworkObject]
                                                 delegateQueue:nil];
     }
}

+ (void)demoBackGroundSessionWorks
{
     NSURL * url = [NSURL URLWithString:@"https://www.wallpapereast.com/static/images/wallpaper-photos-42.jpg"];//@"http://www.hdwallpapersinn.com/wp-content/uploads/2012/09/HD-Wallpaper-1920x1080.jpg"];
     //NSURLSessionConfiguration * backgroundConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:@"backgroundtask1"];

     [[CustomNetwork sharedNetworkObject] prepareBackgroundSession];

     NSURLSessionDownloadTask * downloadTask =[backgroundSession downloadTaskWithURL:url];
     [downloadTask resume];

}

The code that fails is given :

    +(void)createDownloadConnectionWithUrl:(NSString *)aUrl
                                                    operationKey:(NSString *)operationKey
                                                       jsonParam:(NSString *)jsonString
                                                      HTTPMethod:(NSString *)method
                                                  startImmediate:(BOOL)startImmediate
                                                    downloadPath:(NSString *)downloadPath

    {
         if([[aUrl trimmedString] length] && [self isValidMethod:method] && [[downloadPath trimmedString] length])
         {
              CustomURLRequest *dataRequest = [CustomNetwork requestURLWithString:aUrl];
              dataRequest.downloadDestinationPath = downloadPath;

              [self prepareTheDataRequest:&dataRequest
                           WithParameters:&jsonString
                                ForMethod:&method
                          andOpertaionKey:operationKey];


              // Remove any file if present in the path
              if([[NSFileManager defaultManager] fileExistsAtPath:downloadPath])
              {
                   [[NSFileManager defaultManager] removeItemAtPath:downloadPath error:nil];
              }

              // PS : if you are using BackGroundSession, completion block wont work and will give to a NSGenericException. Reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'

              [[CustomNetwork sharedNetworkObject] prepareBackgroundSession];
              NSURLSessionDownloadTask *downloadTask =  [backgroundSession downloadTaskWithRequest:dataRequest];

if(startImmediate)
          {
               [downloadTask resume];
          }
}

The prepare method adds headers to the request.

The very same code that fails , will work if I use dataSession instead of backgroundSession

Q: The same request works in defaultConfiguration but fails in backgroundSession. Am I missing something or is it something to do with "Only upload and download tasks are supported (no data tasks)" doctrine of Apple Docs.

  • you can't use `data task` with `backgroundsessionconfiguration` because you have to give `fileurl` to `upload from` or `download to`. Show your code so that someone can help! – Ketan Parmar Nov 07 '16 at 06:49
  • I've added some code, but still it isn't very clear. BackgroundSessions will not work if I want to download data responses, but work if I want to download images or files ? – Debanjan Chakraborty Nov 07 '16 at 07:00
  • what happen if you call `createDownloadConnectionWithUrl` with defaultsessionconfiguration instead of background? – Ketan Parmar Nov 07 '16 at 07:19
  • It works when I use defaultConfiguration . The delegate method - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location is called and the a *.tmp file is created in the 'location' – Debanjan Chakraborty Nov 07 '16 at 07:26
  • [refer this so post, it may help!](http://stackoverflow.com/questions/23731043/how-to-get-server-response-data-in-nsurlsession-without-completion-block) – Ketan Parmar Nov 07 '16 at 07:33

1 Answers1

0

Background sessions don't care what they're downloading (a file or arbitrary data or whatever). You just have to use a download task or upload task, not a data task. But you're doing that.

My guess, without seeing the rest of the code, is that CustomURLRequest is a subclass of NSURLRequest. Unfortunately, subclasses of NSURLRequest are not supported with NSURLSession—particularly subclasses that actually add properties directly. In various versions of iOS and OS X, the bugs you'll encounter range from serious to completely and utterly broken. I've even seen situations where you get back custom property values from one request along with the URL and headers from a completely different request.

Instead of subclassing, you should create a custom category on NSURLRequest. In that category, create getter and setter methods.

  • Use [NSURLProtocol propertyForKey:inRequest:] for your getters.
  • Use [NSURLProtocol setProperty:forKey:inRequest:] for your setters.

IMPORTANT: All objects stored inside an NSURLRequest using these methods must be property-list-serializable. If you need to associate instances of custom classes with an NSURLRequest, you should store them in a separate NSDictionary, and use an arbitrary dictionary key (e.g. the result of calling [[NSUUID UUID] UUIDString]), which you can safely store in the NSURLRequest object and later use to obtain the custom classes from the external dictionary.

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • CustomURLRequest is indeed a subclass of NSURLRequest. – Debanjan Chakraborty Nov 08 '16 at 09:42
  • One more thing, I wanna ask. How can I set different timeOut s for different NSURLrequests? Is the only way is to create a session with the said timeout ? – Debanjan Chakraborty Nov 08 '16 at 09:48
  • You can set `timeoutInterval` on the request itself (either via a property on a mutable request or by passing in a timeout value when you create a request), which overrides `timeoutIntervalForRequest`. The only way to set `timeoutIntervalForResource` is to create a new session, but you probably don't want to set that anyway. (The default value is seven days.) – dgatwood Nov 08 '16 at 17:45