NSURLSession: Synchronous task hangs - ios

I have recently replaced NSURLConnection to NSURLSession in my code.
As I am using many synchronous url-requests and NSURLSession doesn't support one, I used semaphores to make NSURLSessionDataTask synchronous.
I referred this link: https://forums.developer.apple.com/thread/11519
I have a singleton 'Network Manger' with NSURLSession as a member variable. NSURLSession is instantiated only once and tasks are added to it.
But now synchronous request cause performance issues in my app. There are lags and app hangs when synchronous request is sent.
Her is the call to synchronous request:
NSURLResponse *urlResponse;
NSError *error;
id serverResponse=[[MyNetworkManager sharedInstance] synchronousDataTaskWithRequest:request
returningResponse:&urlResponse
error:&error];
Everything works perfectly if I do not make NetworkManager as singleton and instantiate it everytime.
MyNetworkManager *manger=[[MyNetworkManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] responseSerializer:nil];
NSURLResponse *urlResponse;
NSError *error;
id *serverResponse=[manger synchronousDataTaskWithRequest:request returningResponse:&urlResponse error:&error];
Notes:
1.Exact Steps: There are total 3 (2 async and 1 sync ) NSURLSessionDataTasks. Second async task is called in completion handler of first task. Third synchronous task is called from completion handler of second. This third task is blocked forever and code doesn't proceed.
2.Everything is happening on background thread.
Why does synchronous data task work only if it is added to a new NSURLSession? Why it doesn't work if added to NSURLSession which has already executed two data tasks?

I was facing the similar issue when I used to synchronus implementation using Semaphore and here is the cause of that issue :
I think that your an HTTP request is causing a redirection, and in the willPerformHTTPRedirection method, you haven't called the completionHandler for the new request. Try implementing the method as :
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSLog(#"willPerformRedirection");
completionHandler(request);
}

Related

Allow Connection For Invalid SSL Pinning on AFNetwroking

I'm trying to implement ssl pinning in our iOS app.
The definition as declared by our security architect was to implement it in 2 phases:
- 1st phase is to send an analytics event each time we recognise a bad pinning happening (if the pinning fails, I should send event, but allow the request to continue)
- 2nd phase is to actually block each call that doesn't successfully pass pinning.
We have 2 network layers in the app, one of them uses Alamofire, the other one is using AFNetworking. I'm successfully implemented phase 1 in the Alamofire based network layer. My issue is with the AFNetworking:
AFNetworking is blocking any call that fails the pinning, once a policy is set. Unfortunatly, I would like it to allow these calls.
I tried setting the policy's allowInvalidCertificates property to YES, which let all those failing pinning to continue as needed, but I'm not sure how can I be notified that a specific pinning was failed (in order to send the event)
Any help would be much appreciated.
UPDATE
So I figure out I can set a block to be called whenever a challenge is raised using:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
and/or
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
This allow me to make the validations on the challenge, and for the 1st phase I could send the event and return NSURLSessionAuthChallengePerformDefaultHandling which is exactly what I need.
For some reason, the task block is not being invoked no matter what I do, while the session block is.
So I figure out I can set a block to be called whenever a challenge is raised using:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
and/or
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
This way I can get do whatever side effect needed (in my case, send event with relevant data) and decide if to block the request if needed or let it go on as no policy was configured...

No Response during web service call

I want to do a web service call on a separate thread. I am using the below code,
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// My web service request goes from here
});
The request goes successfully, however there is no response coming back, I am not able to figure out why.
If I use
dispatch_async(dispatch_get_main_queue(), ^{
// My Web service request goes from here
});
Response is coming back but UI is blocked till then, I want to do the web service request in a separate thread without blocking the UI.
The idea is to use a background thread for the request, but handle the result (which changes UI) on the main. This answer shows how (basically by nesting your main queue block inside your background block), but there's a simpler way provided by NSURLConnection:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http..."]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// update the UI here
}];
This handy method performs the request off the main, and -- since you pass it the main queue -- performs the completion block on the main.

iOS 7 background upload and POST requests

I need to upload files in the background with other HTTP POST requests before and after each upload.
Previously I was using beginBackgroundTaskWithExpirationHandler which was working perfectly till iOS 6 but from IOS 7 it is restricted for approx 180 seconds only which is a concern.
I have read the documents regarding NSURLSession were in we have Background transfer service. But the problem with this is it only allows upload and download in background. It doesn't allow me to make POST request after every upload in the background.
So is there any way to make the POST request along with the background uploads?
Any hint in the right direction would be highly appreciated.
I think you can use NSURLSessionDownloadTask to send a POST.
IMO, download task doesn't mean it is used for download. it means the response of your POST request (json/xml) will be downloaded to a local file. Then you can open that file and parse it to get the request.
if you want you can even use a NSURLSessionDownloadTask to upload files to S3. and the s3 response will be 'downloaded' to a local file..
for more information, see this question in the apple developer forum https://devforums.apple.com/thread/210515?tstart=0
I successfully round-trip a vanilla http call during a background task's callback, in production code. The callback is:
-[id<NSURLSessionTaskDelegate> URLSession:task:didCompleteWithError:]
You get about 30 seconds there to do what you need to do. Any async calls made during that time must be bracketed by
-[UIApplication beginBackgroundTaskWithName:expirationHandler:]
and the "end task" version of that. Otherwise, iOS will kill your process when you pop the stack (while you are waiting for your async process).
BTW, don't confuse UIApplication tasks (I call them "app tasks") and NSURLSession tasks ("session tasks").
If you use uploadTaskWithRequest:fromData:completionHandler: you can make your HTTP POST request from the completion handler block:
[backgroundSession uploadTaskWithRequest:request fromData:data completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestForURL:[NSURL URLWithString:#"http://somethingorother.com/"]];
request.HTTPMethod = #"POST";
.
.
.
NSURLResponse *postResponse;
NSError *postError;
NSData *postResponseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&postResponse error:&postError];
// Check postResponse and postError to ensure that the POST succeeded
}
}];

NSURLSessionDownloadTaskDelegate JSON response

I am running a background NSURLSession session and i am trying to figure out a way to get the JSON response out of one of the NSURLDownloadTaskDelegate callbacks. I have configured my session to accept JSON responses.
NSURLSessionConfiguration *backgroundSession = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.Att.Locker.BackgroundUpload"];
backgroundSession.HTTPAdditionalHeaders = #{ #"Accept":#"application/json"};
session = [NSURLSession sessionWithConfiguration:backgroundSession delegate:uploader delegateQueue:nil];
I can easily parse JSON response for NSURLSessionDownloadTasks using the following callback. It writes the JSON response onto the sandbox in the form of NSURL.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
//Reading the Json response from the sandbox using the NSURL parameter
}
My problem is if i encounter an error the callback above is not called, it seems to only get invoked in case of a successful download. Since i am using a background session i cannot use any of the NSURLSessionDataDelegate callbacks. I can only use the NSURLSessionDownloadTaskDelegate and NSURLSessionTaskDelegate and while i can get the task response using the following callback. I don't see the JSON in the response.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)downloadTask.response;
NSDictionary *httpResponse = [response allHeaderFields];
NSLog(#"Response Header Fields:%#",[httpResponse allKeys]);
}
NSURLConnection has a didReceiveData parameter which gives us an NSData object which we can use to get the JSON response. I don't see that in the delegate callbacks for NSURLSession except for NSURLDataTask but we cant use data tasks in the background so how are we supposed to get the JSON response out ? Any help is appreciated
EDIT:
I usually experience this issue while i am running the app in the background (mostly when it is kicked out memory and not just suspended). I have implemented the callbacks in the appDelegate and i am able to re associate with the session.I think didFinishDownloadingToURL is only invoked in case of successful completion of a task but when a task fails there is no guarantee its going to be called but on the other hand didCompleteWithError gets called every time there is a failure
Using a download task, you can get the data using the didFinishDownloadingToURL as you stated.
All NSURLSession tasks have this delegate as well. If you get in here and error is not nil, then walah you have an error. It does not need to complete to get in here. If it does get in here with an error, then the delegate didFinishDownloadingToURL will not be called.
If there is no error, and all of your data downloads, than both delegates will be called.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(#"didCompleteWithError");
}
EDIT:
So something has to bet not setup correctly as there has to be a way to get the data.
Are you implementing application:handleEventsForBackgroundURLSession:completionHandler: in your AppDelegate which will hook your app back up to the completion handler to get the delegate calls?
I highly recommend watching the 2013 WWDC session #705, "Whats New in Foundation Networking". Background session talk begins at around 32 minutes, and the code demo begins around 37:50

AFNetworking globally prompt user to login on 401 error code?

I have been struggling to find a clean solution for this problem for a few I have created an app which makes multiple restful web service requests which work fine however part of the request the login details or API Key could expire and I need to be able to handle this and present the user the login screen again.
In my API Client class I am doing the following which works fine, however because the app does multiple web service requests I am seeing the UI AlertView multiple times.
Any ideas on how I can make this block of code only run once for the first error which occurs and only show one alert view?
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSInteger statusCode = [operation.response statusCode];
if (statusCode == 401) {
[UIAlertView error:#"Your session has expied, please log in again."];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"Logout"
object:self];
} else {
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}
}];
One way would be to create a global variable which contains the current login status. You should check this login status before a request or before the success/failure blocks if the requests are not chained together.
A better approach would be to create a NSOperationQueue to manage the AFJSONRequestOperation objects. This would give you more control over the lifespan of each request. If one returns a 401 then you could cancel all the operations in the queue.
You can find more about creating and using queue here at this link.
Typically you encounter a similar issue when initialising the shared instance of a singleton object that you want to avoid performing the initialisation more than once.
One way to solve this is using Grand Central Dispatch's dispatch_once, as shown below, which is also included in Xcode as a default snippet (GCD: Dispatch Once). In your case you'd present the alert inside the block you pass to dispatch_once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/*code to be executed once*/
});

Resources