NSURLSession doesn't return data - ios

I try to download a zip-archive using NSURLSessionDataTask.
I am aware that there is a NSURLSessionDownloadTask, but the point is I want a didReceiveData callback (to show the progress).
The code is:
NSURLRequest *request = [NSURLRequest requestWithURL:#"..."
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.underlyingQueue = dispatch_get_main_queue();
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:myQueue];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request
completionHandler:^( NSData *data, NSURLResponse *response, NSError *error){ ... }
[task resume];
My class conforms to NSURLSessionDataDelegate.
When I call the method, after several seconds debugger goes to completionHandler with nil data and nil error.
What am I doing wrong?
I also tried:
calling without completionHandler, then debugger goes to didReceiveResponse callback with 200 response and that's all.
using [NSOperationQueue new] for the queue
using [NSURLSession sharedSession] - didn't get any response
using [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: #"..."] - falls saying that I can't use a completionHandler, but without it - also no response.

So I have found the answer and it's not quite obvious from documentation:
I had several callbacks, and among them didReceiveResponse.
Turns out I have to call completion handler in order for the future callbacks to work, i.e:
completionHandler(NSURLSessionResponseAllow);
And one more thing: didCompleteWithError is actually the delegate that tells about successful finish, too, although the name implies that this is the error handler.
What it means: when a download is successfully finished, this function is called with error = nil.
Hope this will be useful for somebody someday.

Related

Make http DELETE request with NSURLSession

I'm trying to make an http DELETE request using NSURLSession, but it's not completely working. The server deletes the resource, but the NSURLSession method dataTaskWithRequest: completionHandler: returns a time out error after waiting for the specified timeout.
I am not using NSURLConnection because it is deprecated.
Of the NSURLSession methods to use, I chose dataTaskWithRequest because it is most similar to the method I use for http GET: dataTaskWithUrl: completionHandler. The methods beginning with "uploadTask" and "downloadTask" don't seem appropriate for a DELETE, but downloadTaskWithRequest: completionHandler: 'worked' in the same way as the dataTask method above. The server deleted the resource, but the method returned a time out error.
Here is the code:
+(void)httpDelete: (NSString*)url completionHandler: (void(^)(id, NSError*))complete
{
NSURLSessionConfiguration *urlSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableDictionary* dictionaryAdditionalHeaders = [[NSMutableDictionary alloc] init];
NSString* stringBearerToken = #"...";
NSString* stringApiKey = #"...";
[dictionaryAdditionalHeaders setObject:stringBearerToken forKey:#"Authorization"];
[dictionaryAdditionalHeaders setObject:stringApiKey forKey:#"x-api-key"];
[dictionaryAdditionalHeaders setObject:#"application/json" forKey:#"Content-Type"];
[dictionaryAdditionalHeaders setObject:#0 forKey:#"Content-Length"];
[urlSessionConfiguration setHTTPAdditionalHeaders: dictionaryAdditionalHeaders];
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration: urlSessionConfiguration delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSMutableURLRequest* mutableUrlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
[mutableUrlRequest setHTTPMethod: #"DELETE"];
[[urlSession dataTaskWithRequest:mutableUrlRequest completionHandler: ^(NSData *data, NSURLResponse* response, NSError* error)
{
if(error != nil)
{
complete(response, error);
}
else
{
complete(response, nil);
}
}] resume];
}
Using Postman, the DELETE call returns with a 204 immediately.
Am I using NSURLSession correctly for a delete request?
It turns out the Amazon API Gateway incorrectly sends a Content-Length header with a 204 response. They added the issue to their backlog March 21, 2016 according to this AWS forum. When I increased the timeout interval of the NSMutableURLRequest to a ridiculous 300 seconds, the dataTaskWithRequest method returns with a real response instead of timing out.
This isn't an error with NSURLSession - it means that your request is actually timing out. That means that there's an error on the back-end (maybe it's not reaching your server at all?)
Also, I've found these issues much easier to debug using a third-party framework to send my HTTP requests. AFNetworking is a really good one.

Perform some task while NSURLConnection loading data objective-c

I am a beginer in iOS programming. I have some problem with NSURLConnection: I have installed SWRevealViewController https://github.com/John-Lluch/SWRevealViewController and when my app is loading Data from server, I can't use interaction with screen. I can't open my SWR-menu while Data is loading.
Here is my SWR in viewDidLoad:
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController ) {
[self.openMenyItmet setTarget: self.revealViewController];
[self.openMenyItmet setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
After that, I called Get method in viewDidLoad:
[self GetQUIZ];
Method detail:
- (void)GetQUIZ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *url = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
[request setURL:[NSURL URLWithString: url]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:[UIDevice currentDevice].name forHTTPHeaderField:#"device"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Left menu, User details: %#", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSLog(#"%#", [request allHTTPHeaderFields]);
if(conn) {
NSLog(#"Connection Successful");
} else
NSLog(#"Connection could not be made");
And then I use data in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *deserr = nil;
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseData options: 0 error: &deserr];
I read that i should use async methods, but I never use it before. Would you write some detail solution ?
Maybe, does have different path?
I would be very grateful for the help!
I'd suggest starting with NSURLSession, which is a modern API that will accomplish the same thing, asynchronously.
To use NSURLSession, you need a few piece of the puzzle:
A web address to reach, and optionally any payload or custom headers.
An instance of NSURL: where you're downloading from and an NSURLRequest to wrap it in.
An NSURLSessionConfiguration, which handles things like caching, credentials and timeouts.
The session itself.
You need an NSURLSessionTask instance. This is the closest object to your NSURLConnection. It has callbacks via delegate or a completion block, if you just need to know when it finishes.
Here's how this would look in code:
// 1. The web address & headers
NSString *webAddress = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
NSDictionary <NSString *, NSString *> *headers = #{
#"device" : [UIDevice currentDevice].name,
#"Content-Type" : #"application/x-www-form-urlencoded"
};
// 2. An NSURL wrapped in an NSURLRequest
NSURL* url = [NSURL URLWithString:webAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3. An NSURLSession Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setHTTPAdditionalHeaders:headers];
// 4. The URLSession itself.
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 5. A session task: NSURLSessionDataTask or NSURLSessionDownloadTask
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 5b. Set the delegate if you did not use the completion handler initializer
// urlSession.delegate = self;
// 6. Finally, call resume on your task.
[dataTask resume];
This will run asynchronously, allowing your UI to remain responsive as your app loads data.
When you send a request on the main thread, like you are doing now, your UI, which is always performed on the main thread, is blocked, waiting for the request to finish and process. So you should perform all your network on a background thread, asynchronously. I would recommend first to check the networking library AFNetworking , it could simplify most of your networking problems.
Welcome to SO. You should know that NSURLConnection was deprecated in iOS 9. You should be using NSURLSession instead. The approach is very similar. You can take the NSURLRequest you've created and pass it to the sharedSession object, which is set up for async requests. The simplest way to deal with it is to use the call dataTaskWithRequest:completionHandler:, which takes a completion block. In your completion block you provide code that handles both success and failure.

Cookies handling with JSONModel/JSONHTTPClient in iOS

In my current project I am using, +(void)postJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock; method to create account and log in my application for the very first time . For second time and onwards, log in call is not there, application directly opens the user's profile screen. But when I am updating user profile (name, contact number etc.), I am getting response status code 403 from by the statement
NSLog(#"Response status code = %i", (int)response.statusCode);
added in implementation of method
+(NSData*)syncRequestDataFromURL:(NSURL*)url method:(NSString*)method requestBody:(NSData*)bodyData headers:(NSDictionary*)headers etag:(NSString**)etag error:(JSONModelError**)err
403 is generally invoked due to authorization failure in server side.
Is there any way to see what are the cookies are going to server side while I am making an API call with
+(void)postJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock;?
JSONModel's built-in networking support is intentionally very basic/primitive/limited. It does not support custom authentication, cookies, etc.
You'd be best making a request with NSURLSession to get the data, then use JSONModel just for the deserialization - rather than for the whole request.
e.g.:
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:#"https://example.com/mydata.json"];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
MyModel *obj = [[MyModel alloc] initWithData:data error:nil];
}];
[task resume];

Models, blocks and completion handlers Oh MY

I'm starting from scratch learning iOS programming.
I want my app to pull XML from a website. I'm thinking that to conform with the MVC pattern I should have a model class that simply provides a method to accomplish that (maybe have it parse the XML too and return an array).
Trouble is that all the tutorials I have found teach the NSURLSession in the context of the view and controller - so edit the appdelegate, or create a view controller, etc.
I got the following method from Apples documentation and I currently have it running as an IBAction when a button is pressed (so I can run it and test it easily). I'd like to get it working then put it in it's own class:
__block NSMutableData *webData;
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString:url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSLog(#"Got response %# with error %#.\n", response, error);
NSLog(#"DATA:\n%#\nEND DATA\n", [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]);
webData = [[NSMutableData alloc] initWithData:data];
}
]resume];
My immediate question is:
Can someone explain how the completion handler is working and how to get data out of there? It's working, data is grabbing the xml from the website and logging it on the console, but copying it to webData doesn't work, it compiles but doesn't copy. (I'm still figuring out why the __block declaration allows webData to sneak in there in the first place!)
My bigger question would be if everyone thinks the idea of a separate model class for this process is a good idea. Is there a better way of designing this?
Thank you!
This may be just some confusion about how asynchronous blocks work. If you're doing this:
__block NSMutableData *webData;
// ...
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString:url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSLog(#"within the block, did I get data: %#", data);
webData = [[NSMutableData alloc] initWithData:data];
}]resume];
NSLog(#"after the block, did I get data: %#", webData);
You might be seeing output that looks like this:
after the block, did I get data: (null)
within the block, did I get data: <NSData ...
What gives? Why did the code after the block run first? And where was the data? The problem is with our definition of "after". The NSLog that appears after the block actually runs before the block runs. It runs as soon as the dataRequest is started. The code inside the block runs after the request has finished.
Keeping the data result in a block variable local to that method does you no good. The value is uninitialized when you hit the end of the method. The block initializes it when it the block runs, but the value is discarded as soon as the block finishes.
Fix: do your handling of the data within the block. Don't expect it to be valid until after the block runs (which is well after the method runs):
EDIT - It's 100% fine to use self inside this block to call methods, set properties, etc. You need to watch out for retain cycles only when the block itself is a property of self (or a property of something self retains), which it isn't...
// don't do this
//__block NSMutableData *webData;
// ...
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString:url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSLog(#"within the block, did I get data: %#", data);
NSMutableData *webData = [[NSMutableData alloc] initWithData:data];
// do whatever you plan to do with web data
// write it to disk, or save it in a property of this class
// update the UI to say the request is done
[self callAMethod:data]; // fine
[self callAnotherMethod]; // fine
self.property = data; // fine
}]resume];
// don't do this, there's no data yet
//NSLog(#"after the block, did I get data: %#", webData);

multiple download NSURLSession

I have a program that download a video from a url using NSURLSession, but i'm not able to do multiple download at the same time.
How can i do it?
How can i manage multiple simultaneous download?
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *getVideo = [session downloadTaskWithURL:fileURL
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
receivedData = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
NSLog(#"%s receiveData:%d",__FUNCTION__,[receivedData length]);
});
}];
[getVideo resume];
From the code you have provided above you are not using any of the properties of NSURLSessionConfiguration class that would enable better download performance.
First of all I would look suggest using your own delegate queue. If you do not provide a queue then the session creates a serial operation queue for all delegate and completion handler calls see the "Creating a Session" section of the NSURLSession Class Reference document for more detail. You can look at the following properties of NSOperationQueue to help improve performance;
qualityOfService
maxConcurrentOperationCount
Next I would look at NSURLSessionConfiguration properties that may help.
HTTPMaximumConnectionsPerHost
HTTPShouldUsePipelining
Finally you should review the section "Life Cycle of a URL Session with Custom Delegates". You should confirm whether your using the delegate methods of NSURLSessionTaskDelegate and NSURLSessionDownloadTaskDelegate or just the completion handler.
You need to put more time into configuring NSURLSession to support the work you want to do.

Resources