NSURLSession delegate methods are not called - ios

I'm trying to update an old application to move from NSURLConnection to NSURLSession. Everything works fine when I use blocks for NSURLSession:
NSURLSessionDataTask *dataTask = [session
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {}
But sometimes I need to analyse the data that the app is receiving from server, so I need didReceiveResponse and didReceiveData methods, similar to the ones used with NSURLConnection. I'm trying to do it like this:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://www.apple.com"]];
[dataTask resume];
But my delegates are never called:
- (void)URLSession:(NSURLSession *)session
NSURLSessionTaskDelegatedidReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSLog(#"-----------------------> NSURLSessionTaskDelegatedidReceiveResponse");
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(#"----------------> didReceiveData");
}
Same thing happens when I'm trying to use other types of tasks (NSURLSessionDownloadTask for example), their delegates are never called. I can actually see in the debugger that the tasks are switching to running state, but I can never get any calls to delegates.
In my header file I do conform my ViewController to delegates:
#interface ViewController : UIViewController <NSURLSessionDelegate, NSURLSessionDataDelegate, NSURLSessionTaskDelegate>
I did quite a bit of searching but even when I'm trying some working examples from the web they just stop working as soon as I try to paste them in my project. For example this tutorial works perfectly for me (I do have to add Transport Security permissions to plist file, but that's about it). But when I'm trying to use this code in my project delegates are never called. And when I'm actually paste my code to another simple application it works just fine. So something probably wrong with my project's settings? Could you please show me what can cause something like this? I'm completely bewildered at the moment.

You are using completion blocks. That exclude delegate calls.
https://developer.apple.com/reference/foundation/nsurlsessiondatadelegate?language=objc
Completion handler block are primarily intended as an alternative to
using a custom delegate. If you create a task using a method that
takes a completion handler block, the delegate methods for response
and data delivery are not called.
And the method you want to use has a different name to
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler

Related

Passing response to controller while converting NSURLConnection to NSURLSession

I have something like service API class which implements NSURLConnectionDelegate methods. I noticed that some of the methods of this are deprecated and Apple now suggests to use NSURLSession. I created this service API's own delegate which the calling class would implement and that would be executed when a response is received. I am looking for how I can do something similar while using NSURLSession.
My present stuff with NSURLConnection:
#class ServiceAPI;
#protocol ServiceAPIDelegate <NSObject>
- (void)getResponseData:(NSData *)responseData;
#end
#interface ServiceAPI : NSObject<NSURLCoDelegate>
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest;
#property (nonatomic, weak) id <ServiceAPIDelegate> delegate;
#end
In ServiceAPI implementation file:
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest {
//[[NSURLSession sharedSession] dataTaskWithRequest:serviceRequest] resume];
self.requestConnection = [NSURLConnection connectionWithRequest:serviceRequest delegate:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.delegate getResponseData:self.responseData sender:self];
}
Class making request and getting response:
#interface CallingController : UITableViewController<ServiceAPIDelegate>
#end
Implementation file CallingController:
- (void)getResponseData:(NSData *)responseData {
// Do something with response.
}
How do I let the calling class handle the response method just like NSURLConnection while using NSURLSession. While reading NSURLSession looks like the request and response are handled together using completion handler something like this:
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
}];
I still want to have a service API class and my controller would just pass down the request to service API to make calls and once the response is back, it would be passed down to controller. How do I pass the response to the controller while using NSURLSession.
Something like this:
#class ServiceAPI;
#protocol ServiceAPIDelegate <NSObject>
- (void)getResponseData:(NSData *)responseData;
#end
#interface ServiceAPI : NSObject<NSURLSessionDelegate>
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest;
#property (nonatomic, weak) id <ServiceAPIDelegate> delegate;
#end
In Implementation
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *downloadTask =
[session dataTaskWithRequest:serviceRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[self.delegate getResponseData:data sender:self];
}];
[downloadTask resume];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// no use
}
You can still achieve what you wish by initialising NSURLSessionDataTask without completion block. That way, it will call the delegate methods instead.
For example:
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"]];
[dataTask resume];
Implement NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;
Also implement NSURLSessionTaskDelegate to know when the request is completed.
/* Sent as the last message related to a specific task. Error may be
* nil, which implies that no error occurred and this task is complete.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error;

Using NSURLProtocol with NSURLSession

My application uses NSURLConnection to communicate with server. We use https for communication. In order to handle authentication from all request in one place i used NSURLProtocol and handled authentication in delegates in that class. Now I have decided to use NSURLSession instead of NSURLConnection. I am trying do get NSURLProtocol working with NSURLSession
I created a task and used NSURLProtocol by
NSMutableURLRequest *sampleRequest = [[NSMutableURLRequest alloc]initWithURL:someURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = #[[CustomHTTPSProtocol class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:checkInInfoRequest];
[task resume];
CustomHTTPSProtocol which is my NSURLProtocol class looks like this
static NSString * const CustomHTTPSProtocolHandledKey = #"CustomHTTPSProtocolHandledKey";
#interface CustomHTTPSProtocol () <NSURLSessionDataDelegate,NSURLSessionTaskDelegate,NSURLSessionDelegate>
#property (nonatomic, strong) NSURLSessionDataTask *connection;
#property (nonatomic, strong) NSMutableData *mutableData;
#end
#implementation CustomHTTPSProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([NSURLProtocol propertyForKey:CustomHTTPSProtocolHandledKey inRequest:request]) {
return NO;
}
return [[[[request URL]scheme]lowercaseString]isEqualToString:#"https"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
- (void) startLoading {
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];
NSURLSession*session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.connection = [session dataTaskWithRequest:newRequest];
[self.connection resume];
self.mutableData = [[NSMutableData alloc] init];
}
- (void) stopLoading {
[self.connection cancel];
self.mutableData = nil;
}
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSLog(#"challenge..%#",challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else {
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
NSLog(#"data ...%# ",data); //handle data here
[self.mutableData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (!error) {
[self.client URLProtocolDidFinishLoading:self];
}
else {
NSLog(#"error ...%# ",error);
[self.client URLProtocol:self didFailWithError:error];
}
}
#end
Start loading is called and also authentication challenge is done but stop loading is called immediately after that.
Error code -999 "Cancelled" is returned after some time. didReceiveData is not called.
Note:NSURLProtocol and the Authentication Process worked fine with NSURLConnection.
What am I missing ?? My Questions are
Registering [NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; worked fine with NSURLConnection but how to Resister for NSURLProtocol Globally with NSURLSession ?.
Why are the requests getting failed in NSURLProtocol(same URL and logic worked withURLConnection) with URLSession and how to get NSURLProtocol working with URLSession ?.
Kindly help me and let me know if you want any more details.
Registering a custom NSURLProtocol with NSUrlsession is same as the way you do with NSURLConnection.
NSURLSession Api's (NSURLSessionDataTask and other task classes) when used with custom NSUrlProtocol fails to handle HTTP Authentication Challenges properly.
This was working as expected on iOS 7.x and is broken in iOS 8.x.Raised a apple radar and my radar was closed saying it was a duplicate of another radar and I Still see the original radar is still open. So I believe this issue is not yet fixed by apple as of today.
Hope I am not too late with the answer :)
I know this is old but the problem you are having is in your didReceiveChallenge method. You are ending the method by calling
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
or
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
What you need to be doing instead is using the completion handler to send your results. It would look like this:
completionHandler(.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
or
completionHandler(.PerformDefaultHandling, nil)
These are in Swift 2.0 but should translate nicely to Obj-C.
My vague recollection is that NSURLSession automatically uses any globally registered protocols. So you shouldn't need to register it again, but it also shouldn't hurt to do so.
I wonder if the URL request is getting copied, in which case your custom tagging might not work, and you might be seeing infinite recursion until it hits some internal limit. Have you tried setting a request header instead?

NSURLSessionDownloadTask Delegates not calling didWriteData method

I'm starting to implement a download method using NSURLSession and successfully downloaded different files from multiple request. But now I wanted to add a progress track, however the delegates for download progress is not being triggered.
Here is my code:
NSURLSessionConfiguration *defaultConfigObject = NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue:nil];
NSURLSessionDownloadTask * downloadTask = [defaultSession downloadTaskWithRequest:request completionHandler:^(NSURL * __nullable location,
NSURLResponse * __nullable response, NSError * __nullable error) {
NSData *data = [NSData dataWithContentsOfURL:location];
[[NSFileManager defaultManager] createFileAtPath:docPath contents:data attributes:nil];
if ([[NSFileManager defaultManager] fileExistsAtPath:docPath]) {
NSDictionary *notificationDic = [[NSDictionary alloc] initWithObjectsAndKeys:docPath,#"docPath", item, #"item", nil];
[[NSNotificationCenter defaultCenter] postNotificationName: #"openFile" object:nil userInfo:notificationDic];
}
}];
[downloadTask resume];
I have the NSURLSessionDownloadDelegate on my header file.
I needed to use completion handler to be able to perform different tasks with the file.
Is there a way I can do it?
If you use downloadTaskWithRequest rendition without the completionHandler parameter, then the progress delegate methods will be called. Obviously, you'll have to move the code currently in the completionHandler block into the didFinishDownloadingToURL method. But if you do this, you'll see didWriteData called.
You will have to initiate your download with:
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
And implement the delegate method for your progress:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
And since you need to perform various tasks when finished, you should also implement this delegate method:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
Essentially, the completion handler routines are "convenience" routines to quickly perform the task and then when finished, perform the completion handler. But they don't call the other delegate routines.
In my case the problem was I conform my class with URLSessionDelegate instead of URLSessionDownloadDelegate. Even if I was implementing the URLSessionDownloadDelegate methods.

How can I receive my data in pieces with using NSURLConnection's sendAsynchronousRequest method?

When I send a synchronous request with NSURLConncetion
[NSURLConnection initWithRequest:myRequest delegate:self];
I can receive my downloaded data in pieces with the following method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.videoData appendData:data];
NSLog(#"APPENDING DATA %#",data);
}
The advantage of this is that I can write my data directly to a file, and limit ram usage when downloading large files.
When I send an asynchronous request, how can I receive my data in pieces? The only place I see the data given back to me is in the completion handler of the request.
[NSURLConnection sendAsynchronousRequest:videoRequest
queue:downloadQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
NSLog(#"All data is given here!");
}];
Is there any solution to this problem? I'm downloading large files in a view controller and want to continue downloading them if the view controller gets dismissed. The problem is that I'm going to use too much memory if I receive all my data at once when downloading large files.
The only method in NSURLConnection which is synchronous is + sendSynchronousRequest:returningResponse:error:
The following methods are all asynchronous
+ connectionWithRequest:delegate:
– initWithRequest:delegate:
– initWithRequest:delegate:startImmediately:
+ sendAsynchronousRequest:queue:completionHandler:
– start
So the code [NSURLConnection initWithRequest:myRequest delegate:self]; itself is an asynchronous, You can use it as it is.
OR
You can make use of NSURLSession for more control
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
}

NSURLSession delegate vs. completionHandler

I've always used completion handlers. With NSURLConnection and now with NSURLSession. It's led to my code being really untidy, especially I have request within request within request.
I wanted to try using delegates in NSURLSession to implement something I've done untidily with NSURLConnection.
So I created a NSURLSession, and created a dataTask:
NSURLSessionDataTask *dataTask = [overallSession dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}];
[dataTask resume];
Right now I have a completionHandler for the response, how would I switch to delegates to manage the response and data? And can I add another dataTask from the delegate of this one? Using the cookies that this dataTask created and placed into the session?
If you want to add a custom delegate class, you need to implement the NSURLSessionDataDelegate and NSURLSessionTaskDelegate protocols at the minimum.
With the methods:
NSURLSessionDataDelegate - get continuous status of your request
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
receivedData=nil; receivedData=[[NSMutableData alloc] init];
[receivedData setLength:0];
completionHandler(NSURLSessionResponseAllow);
}
NSURLSessionDataDelegate
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
// Handle error
}
else {
NSDictionary* response=(NSDictionary*)[NSJSONSerialization JSONObjectWithData:receivedData options:kNilOptions error:&tempError];
// perform operations for the NSDictionary response
}
If you want to separate the delegate code (middle layer) from your calling class (generally its good practice to have separate class/layer for network calls), the delegate of NSURLSession has to be :-
NSURLSession *session=[NSURLSession sessionWithConfiguration:sessionConfig delegate:myCustomDelegateClass delegateQueue:nil];
Ref Links:
NSURLSession Class Reference
iOS NSURLSession Example (HTTP GET, POST, Background Downlads )
From NSURLConnection to NSURLSession

Resources