GCD with NSURLConnection

2019-03-27 13:44发布

I am using GCD to send HTTP request asynchronously. Here is the code that doesn't work:

dispatch_async(connectionQueue, ^{
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];

        [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];


        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        [connection start];//Not working
    }); 

the above code is not working at all. I am not getting any call back in NSURLConnectionDelegate's methods.

But when i tried the following code, everything worked fine and i got proper callbacks

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];

[request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

dispatch_async(connectionQueue, ^{

    [connection start]; // working fine. But WHY ????
});

Can some one please explain this weird behavior of block/GCD?

4条回答
欢心
2楼-- · 2019-03-27 14:14

You can start a temporary NSOperationQueue for that connection. This queue will live only as long as the connection needs it. Basically the NSOperationQueue ensures that you have the delegate callbacks being queued in and processed by spinning a thread to process each delegate callback. (In most cases it is the same background thread that is put to sleep and resumed when new data is downloaded, or when connection fails, connection finishes loading etc.). Once you have this queue setup delegate callbacks will start coming into your application.

dispatch_async(connectionQueue, ^{
    connection = [[NSURLConnection alloc] initWithRequest:request delegate:self     startImmediately:NO];
    NSOperationQueue __autoreleasing *tempQueue = [[NSOperationQueue alloc] init];
    [connection setDelegateQueue:tempQueue];
    [connection start];
});

If you opt for a RunLoop instead then managing the runloop is extra burden on your part.

查看更多
霸刀☆藐视天下
3楼-- · 2019-03-27 14:16

Try this in the first part of your code sample-

dispatch_async(dispatch_get_main_queue(), ^(void){
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection start];
}

If you put the connection in a background queue, it gets shoved away after the queue is complete, and thus you don't get your delegate callbacks. The connection can be in the main queue so it stays in the main run loop for callbacks to occur. Or, you can create your own runloop that handles your background operation for you as suggested by others.

查看更多
Deceive 欺骗
4楼-- · 2019-03-27 14:34

When using NSURLConnection for asynchronous communication, you need to have the thread it is instantiated in attach the connection to its RunLoop in order to have that thread poll for that same connection's delegate methods.

The proper way to instantiate a NSURLConnection asynchronously, without relying on the RunLoop of the main thread is below:

// Done within a Grand Central Dispatch block, or NSOperation.

// We do not start the connection here because we still need to attach the connection to the RunLoop of the current thread and handle how it will communicate responses back to the caller.   
theConnection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

// A previously instantiated NSOperationQueue for managing the delegate callbacks if-and-when they occur.
// [theConnection setDelegateQueue:delegateQueue];
/*
// Other NSURLConnection logic, etc.
*/    
// We start the connection manually after properly establishing how it will poll and respond to events.
[theConnection start];

// This is for the RunLoop of the current thread. (Only needed for < iOS 6 compatibility)
// If this method is executed inside a GCD block or NSOperation, it will be the RunLoop of the thread run in-parallel to the Main Thread.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:30.0]];
// From here, you can alter the time interval to coincide with a specific "time-out" event you'd like to occur.

Where "theConnection" is a member variable of the current class of type "NSURLConnection". Also, you will need to create a NSOperationQueue member variable to manage the delegate callbacks once your connection receives a response. These calls will be asynchronously communicated back to the thread that is running the connection.

From there, you can return the data using the proper NSURLConnection delegate methods.

The benefit of using Grand Central Dispatch, or Operation Queues for your threads, is that the Threading and RunLoop mechanisims are already built in; You will NOT have to manually allocate an additional thread with its own RunLoop inside of it. This eliminates the two-step redundancy of creating a background-thread to manage asynchronous server calls.

I hope this is enough to get you on the right path to creating a truly asynchronous networking model for your application. :)

查看更多
劳资没心,怎么记你
5楼-- · 2019-03-27 14:39

An NSURLConnection will always perform the fetching of data on the thread it was created on (alloc init). This explains why it would work the second way. The first way does work but the thread dies before you are able to receive any information from the NSURLConnection. NSURLConnection already allows for Asynchronous download but if you want to even run the handling of data asynchronously you should use the following method:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

There are some restrictions with that method like authentication is limited and you can't track how much of the document has been downloaded to date. You must also specify an NSOperationQueue that you create, default queue being the main loop queue.

查看更多
登录 后发表回答