NSURLConnection started in another thread. Delegate methods not called - ios

I start a NSURLConnection in another thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
NSURLConnection *connection = [NSURLConnection connectionWithRequest:[request preparedURLRequest] delegate:self];
[connection start];
});
But my delegate method is not called:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data;
When run on the main thread everything is fine. How can I run connection on another thread and get the delegate methods called at the same thread too?

GCD creates, destroys, reuses threads implicitly and there is a chance that the thread you call start from will stop existing immediately afterwards. This may result in the delegate not receiving any callbacks.
If you would like to receive callback in background thread, you can use setDelegateQueue or sendAsynchronousRequest:queue:completionHandler: method:
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
The easiest way to start NSURLConnection in the background thread via GCD is:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURLResponse* response = nil;
NSError* error = nil;
[NSURLConnection sendSynchronousRequest:request] returningResponse:&response error:&error];
NSLog(#"%#", response);
});

Yes, this is well known behavior of NSURLConnection because it needs a run loop to process the delegate events. The most common solution is (a) instantiate it with initWithRequest:delegate:startImmediately: where startImmediately is FALSE; (b) manually scheduleInRunLoop:forMode: to schedule it in the main run loop; and then (c) start the connection.
But, as you have it here, there's no point in dispatching this to a background queue, as it's already asynchronous so you should just initiate this from the main queue and none of the above is necessary. You use the above pattern in special cases (e.g. you were using NSOperation subclass to manage your requests), but generally it's not needed.
Also, FYI, effective iOS9, NSURLConnection is deprecated, so you should be using NSURLSession, anyway. And NSURLSession doesn’t suffer this limitation.

I had a similar issue. What I'm doing now is running NSURLConnection request in the main thread - it is running asynchronously so it won't slow down your application. In connectionDidFinishLoading, I run the following code to process the results of my calls. I perform the check because I have NSURLConnection call which may trigger other network calls. Since they are already running on a background thread I don't want to start a new one.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if ([NSThread isMainThread]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
[self processFinishLoading:connection];
});
}
else {
[self processFinishLoading:connection];
}
}

Related

NSOperation + NSURLConnection

I have created NSOperation class in that class i am calling NSURLConnection to fetch some data.
I am calling NSURLConnection using main thread inside NSOperation class.
NSURLConnection's delegate is set to NSOperation class object.
Call from NSURLConnection comes on main thread.
I need to process this data using the same operation thread. how do i achieve this ??
#implementation ModelCreationSearchOperation {
int try;
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isCancelled) {
[self finish];
return;
}
});
[self fetchData];
}
-(void)fetchData {
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
});
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
[self cancel];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Main thread
//Want to perform parsing of response data on operation thread ....
}
You say that you "want to perform parsing of response data on operation thread." Do you really need to run it on the operation thread, or do you just need to get it off the main thread? The operation queue doesn't necessarily have a single, dedicated thread, so the question doesn't quite make sense. (It's one of the beauties of dispatch queues and operation queues, that it manages the threads for us and we generally don't have to get involved in those details.)
If you simply want the code in connectionDidFinishLoading to run on a background thread (if, for example, you're doing something exceptionally slow in this delegate method), just dispatch it to a background thread (you could use a global queue for that). If you want a serial queue for these connectionDidFinishLoading calls, create your own serial queue for that and dispatch this code to that queue. But if it's not too computationally intensive (e.g. parsing JSON or something like that), you can often just let it run on the main thread without incident.
As an aside, you can, if you really want, create a dedicated thread for your NSURLConnection delegate calls, and schedule the connection on that thread, but it's generally overkill. But see AFNetworking code for example of this implementation. This is illustrated in How do I start an Asychronous NSURLConnection inside an NSOperation?

Error in using asynhronous request in iOS%? [duplicate]

I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!

dispatch_async NSURLConnection - download don't start

I'm trying to download information in background from a url.
I've read about GCD, Runloops and threads and decided that dispatc_async is my way to go.
After receiving data I aalso want to update the gui.
But... the NSUrlConnection don't seem to start at all. The delegate don't receive any calls.
I'v used this NSUrlRequest and NSUrlConnection in a synchronous way and the delegate got the data excpected.
Here is my code, a method in a viewcontroller;
- (void)dispatch: (NSURLRequest *) pRequest respondTo: (VivaQuery *) pQuery {
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(downloadQueue, ^{
NSURLConnection *tConnectionResponse =[[NSURLConnection alloc] initWithRequest: pRequest delegate: pQuery];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Got to main thread.");
[pQuery requestEnd]; // Will update gui, i e aUIView setNeedsDisplay
});
});
}
Anyone got an idea?
Thanks in advance.
Kind regards,
Jan Gifvars
Stockholm
NSUrlConnection does its work asynchronously on it's own background thread so you do not need to create it in a background thread.

Stop UIACtivityIndicatorView temperamental iOS

I do a post request using Async
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
I have the call backs for when the connection has stopped and in here I call my method for stopping the UIActivityIndicatorView
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSLog(#"Connection finish");
[self stopAnimatingSpinner];
}
Heres the stop animating method (I have tried a combination and all of the below stop, remove hide methods
-(void)stopAnimatingSpinner{
[submittingActivity stopAnimating];
submittingActivity.hidden = YES;
[submittingActivity removeFromSuperview];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
Now the problem is sometimes it stops sometimes it doesnt and is very random. If I move the stopping of the activity to the finish parsing of my data instead of relying on the connection callbacks the behaviour is exactly the same. Sometimes they stop sometimes they don't?
The only thing I can think of is that the connection is blocking the main thread but why would it work sometimes and not others?
Two principal problems:
If you use the sendAsynchronousRequest: (etc.) method of NSURLConnection, then only the completion block will be called upon completion, and the connectionDidFinishLoading: delegate method isn't. That's another API.
If you're updating the UI, you should always do so on the main thread. Otherwise your program invokes undefined behavior. So wrap the code which stops the animation in a block that's dispatched on the main thread:
dispatch_sync(dispatch_get_main_queue(), ^{ [self stopAnimatingSpinner]; });
If you are invoking NSURLConnection from main thread, then your completion handler also will be called from main thread. Hence, you should not invoke dispatch_sync when stopping the spinner.

iOS, NSURLConnection: Delegate Callbacks on Different Thread?

How can I get NSURLConnection to call it's delegate methods from a different thread instead of the main thread. I'm trying to mess around with the scheduleInRunLoop:forMode:but doesn't seem to do what I want.
I have to download a large file and it interrupts the main thread so frequently that some rendering that is happening starts getting choppy.
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSRunLoop * loop = [NSRunLoop currentRunLoop];
NSLog(#"loop mode: %#",[loop currentMode]);
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
The other thing I don't see much of is "Modes" There are only two modes documented so not much really to test with.
Any ideas?
Thanks
There are several options:
In your implementation of the delegate methods, make use of dispatch_async.
Start the schedule the connection on a background thread.
You can do the latter like this:
// all the setup comes here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[connection scheduleInRunLoop:loop forMode:NSRunLoopCommonModes];
[loop run]; // make sure that you have a running run-loop.
});
If you want a guarantee on which thread you're running, replace the call to dispatch_get_global_queue() appropriately.
If you want to perform downloads on a separate thread, I'm pretty sure these are the droids you're looking for...
- (void) dispatchRequest{
self->finished = NO;
NSMutableURLRequest* request = //Formulate your request
NSThread* download_thread = [[NSThread alloc] initWithTarget:self selector:#selector(downloadThreadLoop:) object:request];
[download_thread start];
}
- (void) downloadThreadLoop:(NSMutableURLRequest*) request{
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
while(!self->finished]){
//This line below is the magic!
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
//...
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//...
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
//...
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//...
self->finished = YES;
}
If you truly need to do the download in a new thread, it may be easier to detachNewThreadSelector:toTarget:withObject:, setup (and destroy) an NSAutoreleasePool, and then use one of the synchronous selectors like NSData's dataWithContentsOfURL:. This will not make use of the asynchronous NSURLConnectionDelegate.
Because this call is synchronous, it will not return until the file has been downloaded, which will block the main thread, but because you're in a new thread, it won't. Please note that this is typically discouraged behavior. Is there other code happening in the main thread that can be optimized?
NSURLConnection is already doing the download off of the main thread asynchronously. If I understand your question, you would like the delegate messages to be sent on a thread other than the main thread? You can't do that as you can't modify the internal implementation of NSURLConnection. I can think of two ways to simulate this.
Create a sublcass of NSURLConnection (e.g. MyURLConnection) that assigns itself as own delegate. Note that this creates an intentional retain cycle so be careful. MyURLConnection should define a new delegate that supports NSURLConnectionDelegate. Let's call this finalDelegate. When MyURLConnection handles it's own delegate messages, forward or dispatch them to finalDelegate on whatever thread you like.
Similar to option #1 but without the subclass. Handle the NSURLConnection delegate methods on the main thread and forward/dispatch them to whatever thread you like.
The main difference is if you want a reusable subclass that behaves this way or it's a one off implementation.
EDIT: added suggestion on how to run code in the background
If you are going to process the response in the background I would either use operations or grand central dispatch. No need to mess around with run loops and creating threads. Check out Apple's Concurrency Programming Guide.

Resources