No Response during web service call - ios

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.

Related

NSURLSessionTask Never Calls Back When WiFi Off

When I turn off my WiFi connection and run the following code on the iPhone 6s 10.2 simulator, the callback is never executed. I expected the callback to fire fairly quickly with an error like "No Internet connection".
NSLog(#"request-start");
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://www.google.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:0];
task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"request-end");
}];
[task resume];
My Problem
I'm fetching data when the app first loads. If wifi is turned off, I need to show an error. If I set a timeout, it is obeyed - but it would need to be 10+ seconds and I'd rather not make them wait. I've also tried to detect the network status with reachability, but the network status is often unknown when the app is first loaded.
timeoutIntervalForResource
This property determines the resource timeout interval for all tasks
within sessions based on this configuration. The resource timeout
interval controls how long (in seconds) to wait for an entire resource
to transfer before giving up. The resource timer starts when the
request is initiated and counts until either the request completes or
this timeout interval is reached, whichever comes first.
The default value is 7 days.
and
timeoutIntervalForRequest
Important
Any upload or download tasks created by a background session
are automatically retried if the original request fails due to a
timeout. To configure how long an upload or download task should be
allowed to be retried or transferred, use the
timeoutIntervalForResource property.
The default value is 60.
So, without your timeout set, your connection will run for 7 days.
In general an NSURLSession background session does not fail a task if
something goes wrong on the wire. Rather, it continues looking for a
good time to run the request and retries at that time. This continues
until the resource timeout expires (that is, the value in the
timeoutIntervalForResource property in the NSURLSessionConfiguration
object you use to create the session). The current default for that
value is one week!
Source

Syncing new data with Contentful - iOS

I am trying to use the syncing feature of contentful so I will only fetch new or updated content form my space. From the documentation it says with the fetch I need a parameter of "initial"="true", which I am trying below, but I get 400 error. Has someone used this before? thanks
[self.client fetchEntriesMatching:#{#"content_type": #"navigationPage",
#"locale":countryCode,
#"initial":#"true"
}
success:^(CDAResponse *response, CDAArray *array) {
} failure:^(CDAResponse *response, NSError *error) {
}];
`
You're calling the entirely wrong method. You should be calling the initialSynchronizationWithSuccess:failure:which is documented here http://cocoadocs.org/docsets/ContentfulDeliveryAPI/1.10.4/Classes/CDAClient.html#//api/name/initialSynchronizationWithSuccess:failure:
That will in turn call the sync endpoint of the Contentful API with the initial parameter set to true. It will return a CDASyncedSpace object that can be used for further requests. Documented here http://cocoadocs.org/docsets/ContentfulDeliveryAPI/1.10.4/Classes/CDASyncedSpace.html

NSURLSession: Synchronous task hangs

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);
}

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
}
}];

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