NSURLConnection
sendAsynchronousRequest
completionHandler
main thread
iOS development

NSURLConnection and sendAsynchronousRequestqueuecompletionHandler - does the completion block run in the main thread

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The completion block for sendAsynchronousRequest:queue:completionHandler: runs on the NSOperationQueue you pass in. It does not automatically run on the main thread unless the queue argument is specifically NSOperationQueue.mainQueue().

The Short Answer

This method has two separate concerns:

  • the network request itself happens asynchronously
  • the completion handler is dispatched onto the operation queue you provide

So the answer is simple:

  • pass the main queue, and the completion block runs on the main thread
  • pass a background queue, and it runs on that queue instead

That is why UI updates are only safe without extra dispatching when you intentionally use the main queue.

Objective-C Example

objective-c
1NSURL *url = [NSURL URLWithString:@"https://example.com/data.json"];
2NSURLRequest *request = [NSURLRequest requestWithURL:url];
3
4[NSURLConnection sendAsynchronousRequest:request
5                                   queue:[NSOperationQueue mainQueue]
6                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
7    NSLog(@"On main thread: %@", [NSThread isMainThread] ? @"YES" : @"NO");
8}];

Because the queue is [NSOperationQueue mainQueue], the block runs on the main thread.

If you use a custom queue:

objective-c
1NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
2
3[NSURLConnection sendAsynchronousRequest:request
4                                   queue:backgroundQueue
5                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
6    NSLog(@"On main thread: %@", [NSThread isMainThread] ? @"YES" : @"NO");
7}];

Now the block should be treated as background work.

Why This Matters

The queue choice determines what is safe inside the completion block.

If the completion handler runs on the main thread, you can update labels, image views, and other UIKit objects directly. If it runs on a background queue, touching UIKit there is a bug waiting to happen.

A common pattern is:

  1. receive data on a background queue
  2. parse or transform it there
  3. dispatch UI changes back to the main queue

That keeps the interface responsive and still respects UIKit's thread-safety rules.

Example of Background Work Followed by UI Update

objective-c
1NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
2
3[NSURLConnection sendAsynchronousRequest:request
4                                   queue:backgroundQueue
5                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
6    NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
7
8    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
9        NSLog(@"Update UI with %@", text);
10    }];
11}];

This is the safe pattern when the completion block itself is not already on the main queue.

Legacy Context

NSURLConnection is legacy API and has been replaced in modern code by NSURLSession, which gives better control over sessions, tasks, delegates, and background transfers. Still, the threading rule in this old method is clear and useful when maintaining older applications: the completion handler follows the queue you supplied.

One practical debugging trick is to log [NSThread isMainThread] inside old callbacks before touching UI code. In legacy applications, that quick check often explains crashes or visual glitches that only happen after networking completes.

Common Pitfalls

  • Assuming "asynchronous" automatically means "main thread callback."
  • Updating UIKit objects from the completion block when a background queue was passed.
  • Forgetting that the queue parameter controls completion delivery, not just internal request execution.
  • Using NSURLConnection in new code when NSURLSession is the modern replacement.
  • Passing a custom queue and then debugging random UI issues caused by off-main-thread updates.

Summary

  • The completion handler runs on the NSOperationQueue passed to the method.
  • It runs on the main thread only if you pass the main queue.
  • UI updates are safe only when the callback is on the main thread.
  • Background queues are useful for parsing or processing response data.
  • In modern code, prefer NSURLSession, but the same threading discipline still matters.

Course illustration
Course illustration

All Rights Reserved.