Problems Getting Multiple NSURLConnections to Run in Parallel - objective-c

I am am trying to get multiple NSURLConnections to run in parallel (synchronously), however if it is not running on the main thread (block of code commented out below) the URL connection doesn't seem to work at all (none of the NSURLConnection delegate methods are triggered). Here is the code I have (implementation file of an NSOperation subclass):
- (void)start
{
NSLog(#"DataRetriever.m start");
if ([self.DRDelegate respondsToSelector:#selector(dataRetrieverBeganExecuting:)])
[self.DRDelegate dataRetrieverBeganExecuting:identifier];
if ([self isCancelled]) {
[self finish];
} else {
/*
//If this block is not commented out NSURLConnection works, but not otherwise
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}*/
SJLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURLRequest * request = [NSURLRequest requestWithURL:_url];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
} //not cancelled
}//start
Ran through it with a debugger, and after the end of this start method none of the NSURLConnection delegates trigger (I set breakpoints there). But on the main thread it works just fine. Any ideas of what's up? Thanks!

Background threads don't automatically have an active run loop on them. You need to start up the run loop after you create the NSURLConnection in order to get any input from it. Fortunately, this is quite simple:
[[NSRunLoop currentRunLoop] run];
When you say that you are running the connections synchronously, you are incorrect. The default mode of NSURLConnection is asynchronous -- it creates and manages a new background thread for you, and calls back to the delegate on the original thread. You therefore don't need to worry about blocking the main thread.
If you do actually want to perform a synchronous connection, you would use sendSynchronousRequest:returningResponse:error:, which will directly return the data. See "Downloading Data Synchronously" for details.

NSURLConnection needs an active run loop to actually work; the easiest way to ensure this is to just run it from the main thread.
Note that NSURLConnection normally runs asynchronously (and if you run one synchronously, what it really does is run one asynchronously on another thread and then block until that completes), so except for whatever processing you do in your delegate methods it shouldn't have much of an effect on UI responsiveness.

Related

How to run Asynchronous service call with NSOperationQueue or performSelectorInBackground

I am using NSURLConnection asynchronous call to load data from server.While calling service method with performSelectorInBackground,The method gets called but NSURLConnection does not provide any response.I am using asynchronous call because i have to validated authentication first.Whenever i tried service call on main thread it gives me proper data but UI gets freeze.
1) How to make asynchronous call with performSelectorInBackground
what is wrong am i doing by loading data asynchronously.
any help will be appreciated
Here is a possible way though not via NSURLConnection, but it doesn't block:
NSURL *xmlURL = [NSURL URLWithString:URL];
if (self.xmlParser != nil)
{
[self.xmlParser abortParsing];
self.xmlParser = nil;
}
dispatch_queue_t loadQueue1 = dispatch_queue_create("loadQueue1", NULL);
// Use another thread to avoid blocking main thread
dispatch_async(loadQueue1,^{
self.xmlParser = [[[NSXMLParser alloc] initWithContentsOfURL:xmlURL] autorelease];
// -------------------------------------------------------------------
[self.xmlParser setDelegate:self];
dispatch_async(dispatch_get_main_queue(),^{
if (self.xmlParser != nil)
{
[self.xmlParser parse];
}
});
});
dispatch_release(loadQueue1);
Ok,after debugging the code i got the error.It is not the asynchronous call but the updating method after receiving the huge response freezes my UI.Also the NSLog that i put to print the data causes the issue.
If you use NSURLConnection with the asynchronous API, you don't need to use performSelectorInBackground. You call it from the main thread, and you set a delegate, the delegate will be called when data is coming or another event is happening, without blocking the main thread.

How do I wait for a large file to be downloaded?

I have an app that successfully uses the synchronous methods to download files (NSData's initWithContentsOfURL and NSURLConnection's sendSynchronousRequest), but now I need to support large files. This means I need to stream to disk bit by bit. Even though streaming to disk and becoming asynchronous should be completely orthoganal concepts, Apple's API forces me to go asynchronous in order to stream.
To be clear, I am tasked with allowing larger file downloads, not with re-architecting the whole app to be more asynchronous-friendly. I don't have the resources. But I acknowledge that the approaches that depend on re-architecting are valid and good.
So, if I do this:
NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
.. I eventually have didReceiveResponse and didReceiveData called on myself. Excellent. But, if I try to do this:
NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
while( !self.downloadComplete )
[ NSThread sleepForTimeInterval: .25 ];
... didReceiveResponse and didReceiveData are never called. And I've figured out why. Weirdly, the asynchronous download happens in the same main thread that I'm using. So when I sleep the main thread, I'm also sleeping the thing doing the work. Anyway, I have tried several different ways to achieve what I want here, including telling the NSURLConnection to use a different NSOperationQueue, and even doing dispatch_async to create the connection and start it manually (I don't see how this couldn't work - I must not have done it right), but nothing seems to work. Edit: What I wasn't doing right was understanding how Run Loops work, and that you need to run them manually in secondary threads.
What is the best way to wait until the file is done downloading?
Edit 3, working code:
The following code actually works, but let me know if there's a better way.
Code executing in the original thread that sets up the connection and waits for the download to complete:
dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_async(downloadQueue, ^{
self.connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
[ [ NSRunLoop currentRunLoop ] run ];
});
while( !self.downloadComplete )
[ NSThread sleepForTimeInterval: .25 ];
Code executing in the new thread that responds to connection events:
-(void)connection:(NSURLConnection*) connection didReceiveData:(NSData *)data {
NSUInteger remainingBytes = [ data length ];
while( remainingBytes > 0 ) {
NSUInteger bytesWritten = [ self.fileWritingStream write: [ data bytes ] maxLength: remainingBytes ];
if( bytesWritten == -1 /*error*/ ) {
self.downloadComplete = YES;
self.successful = NO;
NSLog( #"Stream error: %#", self.fileWritingStream.streamError );
[ connection cancel ];
return;
}
remainingBytes -= bytesWritten;
}
}
-(void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error {
self.downloadComplete = YES;
[ self.fileWritingStream close ];
self.successful = NO;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.downloadComplete = YES;
[ self.fileWritingStream close ];
self.successful = YES;
}
... didReceiveResponse and didReceiveData are never called. And I've
figured out why. Weirdly, the asynchronous download happens in the
same main thread that I'm using. It doesn't create a new thread. So
when I sleep the main thread, I'm also sleeping the thing doing the
work.
Exactly. The connection is driven by the run loop; if you sleep the thread, the run loop stops, and that prevents your connection from doing its thing.
So don't do anything special. Let the app sit there, with the run loop running. Maybe put a little spinner on the screen to entertain the user. Go about your business if you can. If at all possible, let the user continue to use the application. Your delegate method will be called when the connection is complete, and then you can do what you need to do with the data.
When you move your code to a background thread, you'll again need a run loop to drive the connection. So you'll start create a run loop, schedule your connection, and then just return. The run loop will keep running, and your delegate method will again be called when the connection completes. If the thread is done, you can then stop the run loop and let the thread exit. That's all there is to it.
Example: Let's put this in concrete terms. Let's say that you want to make a number of connections, one at a time. Stick the URL's in a mutable array. Create a method called (for example) startNextConnection that does the following things:
grabs an URL from the array (removing it in the process)
creates an URL request
starts a NSURLConnection
return
Also, implement the necessary NSURLConnectionDelegate methods, notably connectionDidFinishLoading:. Have that method do the following:
stash the data somewhere (write it to a file, hand it to another thread for parsing, whatever)
call startNextConnection
return
If errors never happened, that'd be enough to retrieve the data for all the URLs in your list. (Of course, you'll want startNextConnection to be smart enough to just return when the list is empty.) But errors do happen, so you'll have to think about how to deal with them. If a connection fails, do you want to stop the entire process? If so, just have your connection:didFailWithError: method do something appropriate, but don't have it call startNextConnection. Do you want to skip to the next URL on the list if there's an error? Then have ...didFailWithError: call startNextRequest.
Alternative: If you really want to keep the sequential structure of your synchronous code, so that you've got something like:
[self downloadURLs];
[self waitForDownloadsToFinish];
[self processData];
...
then you'll have to do the downloading in a different thread so that you're free to block the current thread. If that's what you want, then set up the download thread with a run loop. Next, create the connection using -initWithRequest:delegate:startImmediately: as you've been doing, but pass NO in the last parameter. Use -scheduleInRunLoop:forMode: to add the connection to the download thread's run loop, and then start the connection with the -start method. This leaves you free to sleep the current thread. Have the connection delegate's completion routine set a flag such as the self.downloadComplete flag in your example.
I hesitate to provide this answer because the others are correct that you really should structure your app around the asynchronous model. Nevertheless:
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSString* myPrivateMode = #"com.yourcompany.yourapp.DownloadMode";
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:myPrivateMode];
[connection start];
while (!self.downloadComplete)
[[NSRunLoop currentRunLoop] runMode:myPrivateMode beforeDate:[NSDate distantFuture]];
Do not do this on the main thread. Your app is just as likely to be terminated for blocking the main thread as for downloading too big a file to memory.
By the way, given that you're downloading to a file instead of memory, you should consider switching from NSURLConnection to NSURLDownload.
I think your sleepForInterval is blocking the NSURLConnection's activity -
No run loop processing occurs while the thread is blocked.
From the NSThread documentation.
I think you might have to rethink how you're setting your downloadComplete variable. Consider using your connectionDidFinishLoading:connection delegate method to determine when the download is complete instead of your loop + sleep?
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.downloadComplete = YES;
// release the connection, and the data object
[connection release];
[receivedData release];
}
From the NSURLConnection guide.
You can use the connection:connection didFailWithError:error delegate method to ensure you're dealing with situations where the download does not complete.

Concurrency and NSURLConnection

Recently I've been researching and working with NSURLConnection and concurrency.
There seem to be several different approaches, and the ones I've tried (dispatch queues and operation queues) all seemed to work properly, eventually.
One problem I encountered with concurrency and NSURLConnection, is the delegate methods not being called. After some research I found out the NSURLConnection needs to either be scheduled in the main runloop, or the NSOperation should be running on the main thread. In the first case I'm invoking NSURLConnection like this:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
And in the latter case like this:
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
The delegate methods handle everything else, and both seem to work properly.
In the case when I was using a dispatch queue, I did the same as with the first case (schedule the NSURLConnection in the main runloop).
My question is, what's the difference between these two approaches? Or are they actually the same, but just a different way of implementing them?
The second question is, why is this necessary? I'm also using an NSXMLParser inside an NSOperation, and this doesn't seem to require a main runloop or main thread, it just works.
I think I figured it out myself. Since both NSURLConnection and NSXMLParser are asynchronous, they require a run loop for the delegate messages when they're running in the background.
As far as I know now, the main thread automatically keeps the run loop running; the main run loop. So both solutions for NSURLConnection I posted will make sure the main run loop is used for the asynchronous part, either by telling the connection to use the main run loop for the delegate messages, or by moving the entire operation onto the main thread, which will automatically schedule the connection on the main thread as well.
What I've come up with now, is to keep a run loop running on my custom NSOperation classes, so I no longer have to perform any scheduling or thread checking. I've implemented the following at the end of the (void)start method:
// Keep running the run loop until all asynchronous operations are completed
while (![self isFinished]) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

Perform block inside a NSOperation

I have a method in some class which performs some task using a block. When I execute that method using NSInvocationOperation then control never goes to the block. I tried logging inside the block but that is never called actually. But if I simply call that method with instance of that class then everything works as expected.
Don’t blocks run inside NSOperation?
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:myClassObj selector:#selector(myClassMethod:) object:obj1];
[[AppDelegate sharedOpQueue] addOperation:op];
[op release];
- (void)myClassMethod:(id)obj
{
AnotherClass *otherClass = [[AnotherClass allco] init]
[otherClass fetchXMLWithCompletionHandler:^(WACloudURLRequest* request, xmlDocPtr doc, NSError* error)
{
if(error){
if([_delegate respondsToSelector:#selector(handleFail:)]){
[_delegate handleFail:error];
}
return;
}
if([_delegate respondsToSelector:#selector(doSomeAction)]){
[_delegate doSomeAction];
}
}];
}
- (void) fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
_xmlBlock = [block copy];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(_xmlBlock) {
const char *baseURL = NULL;
const char *encoding = NULL;
xmlDocPtr doc = xmlReadMemory([_data bytes], (int)[_data length], baseURL, encoding, (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS));
NSError* error = [WAXMLHelper checkForError:doc];
if(error){
_xmlBlock(self, nil, error);
} else {
_xmlBlock(self, doc, nil);
}
xmlFreeDoc(doc);
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if(_xmlBlock) {
_xmlBlock(self, nil, error);
}
}
You are performing your NSConnection asynchronously (which you don't need to do in an NSOperation because you should already be on a background thread).
After your call to fetchXMLWithCompletionHandler, your method ends. This signals that the NSOperation is finished and it gets released and it's thread gets either reused for something else or, more likely, released as well. This means that by the time you get your callbacks, your initial object doesn't exist anymore!
There are two solutions :
1) Use NSURLConnection synchronously. This will wait in your myClassMethod until it has got a response.
2) Learn about NSOperations's concurrent mode. I don't know if this will work with NSInvocationOperation though :( And it's fairly complicated compared to option (1).
I would use method (1) - you have already created a background thread to perform your operation in, why bother creating another one to do your connection request?
There are two ways of fixing your problem:
The easy way out
is — as Dean suggests — using +[NSURLConnection sendSynchronousRequest:returningResponse:error:], as you already are on a different thread. This has you covered — I'd say — 80-90% of the time, is really simple to implement and Just Works™.
The other way
is only slightly more complicated and has you covered for all the cases where the first method does not suffice — by visiting the root of your problem:
NSURLConnection works in conjunction with the runloop — and the threads managed by NSOperationQueue don't necessarily use (or even have!) an associated runloop.
While calling +[NSURLConnection connectionWithRequest:delegate:] will implicitly create a runloop, if needed, it does not cause the runloop to actually run!
This is your responsibility, when the NSOperationQueue you use is not the queue associated with the main thread.
To do so, change your implementation of fetchXMLWithCompletionHandler: to look similar to the following:
- (void)fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
self.xmlHandler = block; // Declare a #property for the block with the copy attribute set
self.mutableXMLData = [NSMutableData data]; // again, you should have a property for this...
self.currentConnection = [NSURLConnection connectionWithRequest:request delegate:self]; // having a #property for the connection allows you to cancel it, if needed.
self.connectionShouldBeRunning = YES; // ...and have a BOOL like this one, setting it to NO in connectionDidFinishLoad: and connection:didFailWithError:
NSRunLoop *loop = [NSRunLoop currentRunLoop];
NSDate *neverExpire = [NSDate distantFuture];
BOOL runLoopDidIterateRegularly = YES;
while( self.connectionShouldBeRunning && runLoopDidIterateRegularly ) {
runLoopDidIterateRegularly = [loop runMode:NSDefaultRunLoopMode beforeDate:neverExpire];
}
}
With these small changes, you're good to go. Bonus: this is really flexible and (eventually) reusable throughout all your code — if you move the XML-parsing out of that class and make your handler simply take an NSData, an NSError and (optionally) an NSURLResponse.
Since you probably don't want the clients of your loader to see and possibly mess with the properties I just suggested you should add, you can declare them in a class continuation.

Async call in Objective-C

I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.