Running Nsurlsession Completion Handler on Main Thread

Running NSURLSession completion handler on main thread

Yes, just dispatch your main thread stuff using GCD:

 NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:@"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(@"error is %@", [jsonError localizedDescription]);
// Handle Error and return
return;
}

dispatch_async(dispatch_get_main_queue(), ^{
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(@"In main thread--completion handler");
}
else{
NSLog(@"Not in main thread--completion handler");
}
});

}] resume];

Has NSURLSession dataTaskWithRequest completion block always run on Main Thread?

I just figured out the answer. The thread of the completion handler is setup in the init of the NSURLSession.

From documentation:

init(configuration configuration: NSURLSessionConfiguration, delegate delegate: NSURLSessionDelegate?, delegateQueue queue: NSOperationQueue?)

queue - A queue for scheduling the delegate calls and completion handlers. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.

My code that setup for completion on main thread:

var session = NSURLSession(configuration: configuration, delegate:nil, delegateQueue:NSOperationQueue.mainQueue())

Are NSURLSessionDataTask completion blocks called on the main thread?

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).

Get data from a request to the main thread

The dataTaskWithRequest method runs asynchronously, i.e., later. You can’t just reference the variables outside of the block.

The typical solution is to provide a block parameter to your method that performs this request, a completion handler, and call the completion handler inside the asynchronous block of dataTaskWithRequest.


E.g., you might have:

- (void)performRequestWithURL:(NSURL *)url completion:(void (^ _Nonnull)(NSString *username, NSString *domain, NSError *error))completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

NSURLSession *session = [NSURLSession sharedSession];

[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, nil, error);
});
return;
}

NSString *requestReply = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSRange rangeUsername = [requestReply rangeOfString:@"<username>"];
NSString *username = [requestReply substringFromIndex:rangeUsername.location];
username = [username stringByReplacingOccurrencesOfString:@"<username>" withString:@""];
username = [username stringByReplacingOccurrencesOfString:@"</username>" withString:@""];

NSRange rangeDomain = [requestReply rangeOfString:@"<SIP_domain>"];
NSString *domain = [requestReply substringFromIndex:rangeDomain.location];
domain = [domain stringByReplacingOccurrencesOfString:@"<SIP_domain>" withString:@""];
domain = [domain stringByReplacingOccurrencesOfString:@"</SIP_domain>" withString:@""];

dispatch_async(dispatch_get_main_queue(), ^{
completion(username, domain, nil);
});
}] resume];
}

And call it like so:

[self performRequestWithURL:url completion:^(NSString *username, NSString *domain, NSError *error) {
if (error) {
NSLog(@"error = %@", error);
return;
}

// use `username` and `domain` here ...
}];

// ... but not here, because the above runs asynchronously

How does URLSessionTask run

Short answer: The key observation is that the URLSessionTask always runs asynchronously with respect to the thread that you started it. And unless you explicitly specify otherwise, the completion handlers and/or delegate methods will run on a background thread. So, you don't have to use GCD when initiating the request, but in the completion handler we will use GCD to dispatch anything that updates the UI or model to the main queue.


You asked:

  1. Which thread the URLSessionTask is running by default? Main thread or background thread?

There are really two questions there: Which thread(s) URLSession uses internally for its own purposes and which thread the completion handler and/or delegate methods will be run.

On the former question, this is an internal implementation detail that is not documented anywhere, but it appears to create its own (background) thread with a separate run loop to process requests. But these implementation details generally don't really matter: We're assured is that the request runs asynchronously (does not block the current thread).

The latter question, on which thread the completion handlers and delegate methods are called, is generally far more important. Unless we specify otherwise, URLSession runs completion handlers and delegate methods on serial operation queue that URLSession created for us. That means that these run on a background thread.

The only exception to this rule is if you specified OperationQueue.main as the queue parameter when instantiating a URLSession, in which case it would obviously use the main thread for the completion handlers and delegate methods. But even in this case, the request runs asynchronously and URLSession will not block the main thread.


  1. Why current thread shows null in thread name? Does it mean it is running in background thread by default? (I see name="main" for print on main thread)

It's running on a serial operation queue. The threads used by operation queues threads don't generally have names. But you can look at OperationQueue.current?.name to confirm which operation queue is being used.


  1. In general, is it necessary to run URLSessionTask with GCD in order to force it run in background thread or not? I am asking this because I saw some tutorials doesn't use GCD to run URLSessionTask, they only use GCD to run completion handler in main thread.

The flow suggested by those tutorials is correct. You do not have to use GCD when initiating the request. It always runs asynchronously with respect to the queue from which you started it. The only thing you need to do is to dispatch relevant code within the completion handler or delegate method to the appropriate queue.

Specifically, since we generally let URLSession run completion handlers on its own serial queue, we therefore have to dispatch UI updates back to the main queue. Sometimes overlooked, we also generally dispatch model updates back to the main queue, too (or use some other synchronization mechanism).

Does NSURLSession Take place in a separate thread?

Yes,

NSURLSession (URLSession in Swift) does its work in a background thread. The download ALWAYS takes place asynchronously on a background thread.



EDIT:

There is no reason to wrap your code that invokes NSURLSession (or URLSession in Swift 3 or later) in a GCD call.


You can control whether its completion methods are executed on a background thread or not by the queue you pass in in the delegateQueue parameter to the init method. If you pass in nil, it creates a (background thread) serial operation queue where your completion methods are called. If you pass in NSOperationQueue.mainQueue() (OperationQueue.mainQueue() in recent versions of Swift) then your completion delegate methods/closures will be invoked on the main thread and you won't have to wrap UI calls in dispatch_async() calls to the main thread.

Does NSURLSession for HTTP data task (NSURLSessionDataTask) runs in background thread or we will have to provide the queue?

No, you don't need to use GCD to dispatch this to background queue. In fact, because the completion block runs on background thread, the exact opposite is true, that if you need anything in that block to run on the main queue (e.g., synchronized updates to model objects, UI updates, etc.), you have to manually dispatch that to the main queue yourself. For example, let's imagine that you were going to retrieve a list of results and update the UI to reflect this, you might see something like:

- (void)viewDidLoad 
{
[super viewDidLoad];

NSURLSession *session = [NSURLSession sharedSession];

NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// this runs on background thread

NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

// detect and handle errors here

// otherwise proceed with updating model and UI

dispatch_async(dispatch_get_main_queue(), ^{
self.searchResults = json[@"results"]; // update model objects on main thread
[self.tableView reloadData]; // also update UI on main thread
});

NSLog(@"%@", json);
}];

[dataTask resume];
}

Error in background/main thread using API

You need to present your UIAlertController on the main thread because the completion callback of URLSession.shared.dataTask(with:completionHandler:) runs on a background thread.

DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Cancel successful", comment: "Cancel successful"), message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizableOk, style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}

NSURLSessionDataTask completion handler not executing in command line project

A command line interface does not have a runloop by default so asynchronous tasks cannot work.

You have to start and stop the runloop explicitly

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
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);
CFRunLoopStop(runloop);
}];

[dataTask resume];
CFRunLoopRun();
}
return 0;
}

You should handle the dataTask error to exit with a value != 0



Related Topics



Leave a reply



Submit