still timeout after I suspend NSURLSessionDataTask in AFN3.1.0 - ios

I init NSURLSessionDataTask with follow method
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
then call setDataTaskDidReceiveResponseBlock and setDataTaskDidReceiveDataBlock to write data to file.
and then I call suspend but the after the timeoutInterval I get completionHandler with timeout error.
the document about suspend:
/*
* Suspending a task will prevent the NSURLSession from continuing to
* load data. There may still be delegate calls made on behalf of
* this task (for instance, to report data received while suspending)
* but no further transmissions will be made on behalf of the task
* until -resume is sent. The timeout timer associated with the task
* will be disabled while a task is suspended. -suspend and -resume are
* nestable.
*/
I want to know how to suspend the task,and why I call suspend like this it does not work

It also happened to me. I did several tests, it seems that there is nothing with AFN, you can suspend a NSURLSessionDownloadTask, but not NSURLSessionDataTask. I have no idea about the reason.
Update:I found this post: NSURLSessionTask. Suspend does not work

Related

NSURLSession: method dataTaskWithRequest never reach completion callback on lengthy responses

The following code that used to create communication session with a remote server and send/receive HTTP requests/responses.
However, when a large file is attached to the response, the callback block never reached.
Only when explicitly invoke the cancel method after some timeout from the NSURLSession task (_dataTask), this callback is being called.
notice that using tcpdump it can easily observed that the response was properly received on the client side.
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:queue];
_dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if ([error code] == NSURLErrorCancelled) {
writeLog(LOG_ERROR, "NSURLErrorCancelled");
} else {
...
}
}];
[_dataTask resume]
// after timeout, the operation is cancelled.
sleep(100)
[_dataTask cancel];
I'd like to know if using dataTask has response length limit (because it's working for small files on response body) and if there is such a limit, so which other method should I use in order to overcome it.
I saw that there's an alternative method in NSUrlsession dedicated for downloading files called downloadTaskWithRequest but it doesn't have an async completion block.
Thanks !
When fetching large resources, you should use download task. A data task will attempt to load the entire response in a single NSData object. Loading a large asset in memory at the same time is not only inefficient, but if it is extraordinarily large, can cause problems.
A download task is well suited for these tasks, because it will stream the asset to a temporary file for you, reducing the peak memory usage. (Admittedly, you can manually achieve the same with data task with delegate pattern, but download tasks do this for you.)
You said:
I saw that there's an alternative method in NSURLSession dedicated for downloading files called downloadTaskWithRequest but it doesn't have an async completion block.
Two observations:
There is a rendition, dataTaskWithRequest:completionHandler:, that has a completion block:
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
...
}];
[task resume];
Note, I would advise using sharedSession if you are not setting a delegate, or otherwise customizing your NSURLSession. You do not want to instantiate NSURLSession objects unnecessarily. And if you really must instantiate a NSURLSession, re-use it for subsequent tasks and/or make sure to call finishTasksAndInvalidate after submitting the last task for that session, or else the NSURLSession will leak. And, if you instantiate your own NSURLSession, you do not have to instantiate your own operation queue, as it will, by default, create a serial queue for you if you do not supply an operation queue.
The rendition without a block parameter, downloadTaskWithURL:, works, too. All you need to do is to specify a delegate for your NSURLSession and then and implement URLSession:downloadTask:didFinishDownloadingToURL:.
The reason I suggest this is that, often, when we are downloading very large assets (especially over cellular), we realize that the users may want to leave our app and let the download complete in the background. In those situations, we would use a background NSURLSessionConfiguration. And when using background sessions, you must use this delegate-based approach. So, if you think you might eventually adopt background sessions for long downloads, then adopting a delegate-based approach now is not a bad idea.
For more information, see Downloading Files in the Background.

URLSession completion block not executing on delegate queue shown by Xcode

I have configured a URLSession to fetch data from network & have set a delegate queue so that all subsequent operations happen within my queue.
However, when using breakpoints to view the Debug navigator, Xcode shows the completion block of URLSession is invoked on arbitrary threads.
The URLSession is setup as follows -
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:[MyManager sharedInstance].queue];
...
[self.urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
/... operations expected to be executed on WEGQueue .../
}] resume];
Below is screen capture of processes on my serial queue named WEGQueue before URLSession has started.
Now I expect the completion block operations to be invoked on the specified delegate queue of URLSession i.e. WEGQueue here.
However, using a breakpoint to view the Debug Navigator shows that block is being processed on an arbitrary queue. Attached pic below.
Below is Debug Navigator in "View Process by Queue" filter.
This is really weird!
I'm not sure why URLSession is not invoking completion blocks on the specified delegate queue.
And that's not all.
It gets weirder due to the fact that when I do a po, lldb says that the completion block is (as expected) on the WEGQueue. See pic below.
It's confusing that Xcode's Debug Navigator says URLSession's completion block is being executed on an arbitrary thread while lldb says it is executed as expected on the delegate queue WEGQueue.
Did anyone face a similar scenario? Is this just an Xcode GUI bug? Or something is really amiss over here?
I'd say it is just an XCode GUI limitation, that it does not always label the Queue in the "view by queue" with the name that you gave it. And it does not always label the threads with the Queue that thread is currently handling. As you say, when you do po [NSOperationQueue currentQueue], it does report the correct queue. So you have no indication that it actually uses another queue.
There is no guarantee that the same queue is always handled by the same thread. Each event can be handled by a different thread. There is only a guarantee that the events are not running in parallel, for a Serial Queue.

Tracking data usage for network requests using AFNetworking

I am trying to figure out a way to find how much data is sent/received by my requests with AFNetworking. What are some of the best practices to solve this problem?
Example:
do a GET to an endpoint fakeapi/downloadlist
I want to know how many bytes were sent to the server and how many bytes were received back.
Thanks for your help!
Let's take the GET:parameters:success:failure: method on the AFHTTPSessionManager for performing a GET request. The declaration for the method is:
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters
success:(nullable void (^) (NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^) (NSURLSessionDataTask *_Nullable task, NSError *error))failure;
There are two blocks of interest here: One for success (named very smartly success) and another for a failure in the request.
Both of the blocks return a parameter of type NSURLSessionDataTask (which is nullable on the failure event, if that's the case then there was no response, most likely to happen when there's no internet connection). According to the Apple Documentation for NSURLSessionDataTask, which inherits from NSURLSessionTask, it is possible to get the response object some other properties (take a look on the symbols section on the documentation):
int bytesReceived = ((int)[task countOfBytesReceived]);
int bytesSent = ((int)[task countOfBytesSent]);
// Do something with the numbers from here...
Further from here, take a look on the Symbols for NSURLSessionTask and take any other thing you need.

Are NSURLSessionDataTask completion blocks called on the main thread?

I have been trying to figure out when it's okay to "just type in what I need done" and when I need to be specific about what kind of work I am doing on what kind of thread.
As I understand I should only update the UI on my main thread. Does this mean that it's not okay to do something like this? Should I put this into a GDC call?
[sessionManager dataTaskWithRequest:aRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
someUILabel.text = #"Hello!"; // Updating my UI
[someTableView reloadData]; // Ask a table view to reload data
}];
That's it for the UI part. Now, let's assume I had an NSMutableArray somewhere in in my class. I would be adding or removing objects to this array by for instance tapping a UIButton. Then again I have a NSURLSessionDataTask going to a server somewhere to get some data and load it into my NSMutableArray, like so:
[sessionManager dataTaskWithRequest:aRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
myMutableArray = [[responseObject objectForKey:#"results"] mutableCopy];
}];
This is not a UI operation. Does this need to be wrapped in a GDC call to avoid crashing in a race condition between my button-tap adding an object (i.e. [myMutableArray insertObject:someObj atIndex:4];) while the completion block runs, or are these designed to not clash into each other?
I have left out all error handling to focus on the question at hand.
TLDR: It costs you nothing to call dispatch_async(dispatch_get_main_queue()... inside your completion handler, so just do it.
Long Answer:
Let's look at the documentation, shall we?
completionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue.
The delegate queue is the queue you passed in when you created the NSURLSession with sessionWithConfiguration:delegate:delegateQueue:. If that's not how you created this NSURLSession, then I suggest you make no assumptions about what queue the completion handler is called on. If you didn't pass [NSOperationQueue mainQueue] as this parameter, you are on a background queue and you should break out to the main queue before doing anything that is not thread-safe.
So now the question is:
Is it thread-safe to update the UI and talk to the table view? No, you must do those things only on the main queue.
Is it thread-safe to set myMutableArray? No, because you would then be sharing a property, self.myMutableArray, between two threads (the main queue, where you usually talk to this property, and this queue, whatever it is).

NSURLSession - execute two requests and parse two responses, but still block main thread

I want to execute two requests to my remote server and get and parse two response asynchronously, but still block the main thread until both of two processes are done.
Here's my code in AppDelegate.m's application: didFinishLaunchingWithOptions: method:
NSURL *url1 = [NSURL URLWithString: #"url1"];
NSURL *url2 = [NSURL URLWithString:#"url2"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task1 = [session dataTaskWithURL:url1 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//process1
}];
[task1 resume];
NSURLSessionDataTask *task2 = [session dataTaskWithURL:url2 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//process2
}];
[task2 resume];
In this code, I want to fetch data from url1 and parse the response in process1, and also want to do the same thing to url2 and process2.
However, while I want to execute those two processes above asynchronously, I also want to wait for both to finish each work before ending AppDelegate.m's application: didLaunchingWithOptions: method, because an error happens in my first view controller (after AppDelegate.m) if it's initialized before the above two execution are done.
This is easily implemented in some languages such as Python or Go, and I think it can also be implemented in NSURLConnection in Objective-C (though I didn't try, forgive me if it's not the case). However, I want to use NSURLSession in this case, since the actual session configuration is a bit more complicated than the code above.
So is it feasible to use synchronous feature in NSURLSession?
Why would you block the main thread?? This is bad for users, and bad for you.
Change your view controller to show a progress indication instead of crash. This keeps the app active and the user informed of what is happening. If you want you can have a nice animation for the user to watch while they wait.
Its a bad idea to block application launch, it will crash the app if the app didn't launch in launchTime <=24 seconds. If your doesn't lanuch within it will kill the app. So what you should do then. Use your own loader screen that will appear till you finish with api parsing. And for api parsing you can try something like queue, so until all request finishes the loader screen will show.
I hope it will give you some idea.

Resources