runModalForWindow throttles http requests - objective-c

I have url connection, which normally works fine
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:delegate];
But when I create a modal window, no request ever receives response:
[NSApp runModalForWindow:window];
If I comment this line out, thus creating a 'standard' window, everything works.
I tried implementing all methods from NSURLConnectionDelegate, not a single of them called.
I suspect this is something about 'run loops', but have little experience in this area. Does anybody have experience in this?
Thank you

If you're targeting 10.5+, you can tell the NSURLConnection to also run in NSModalPanelRunLoopMode (the mode your current thread's runloop would be in while presenting a modal view) via
-(void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
where aRunLoop would probably be [NSRunLoop currentRunLoop] and the mode would be NSModalPanelRunLoopMode. More info in the NSURLConnection doc.
If you're supporting earlier OSs, you may have to get creative (i.e. with multithreading). Good discussion of this issue pre-10.5 here.

I haven't bumped into the situation you're having, but I suggest spawning and starting a connection in background thread.

I also met the same problem that didn't get the delegate method called when using NSURLConnection in a Modal Window.
after some investigation, following code resolve it.
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:NO];
[conn scheduleRunLoop:[NSRunLoop currentLoop] forMode:NSModalPanelRunLoopMode];
[conn start];
However, when connectionDidFinishLoading called, [NSApp stopModal] doesn't work, need call [NSApp abortModal] instead.

Related

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]];
}

Problems Getting Multiple NSURLConnections to Run in Parallel

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.

Why release the NSURLConnection instance in this statement?

I read this in a book.
-(IBAction) updateTweets
{
tweetsView.text = #"";
[tweetsData release];
tweetsData = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:#"http://twitter.com/statuses/public_timeline.xml" ];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
[activityIndicator startAnimating];
}
In this statement,is that correct to release the "connection" instance at that time? After releasing it which means this NSURLConnection instance will be destroyed since it's reference count is 0 ,how are we going to make this connection operation work after release this instance? THANKS.
I thought we should release it in one of the following callback methods,right?
connectionDidFinishLoading:
connection:didFailWithError:
It's actually fine to release it there, once the connection is sent out via initWithRequest, the only thing that matters is that the delegate exists or I believe the response will just be silently lost.
From what I can tell, the only reason to not release it there is if you want to call [connection cancel] at some point in one of the delegate functions, in which case it would be best to do what you suggest and release it in BOTH connectionDidFinishLoading and didFailWithError since only one of them will be called (right?).
Edit: For a more thorough answer, NSURLConnection initWithRequest is an asynchronous request. So it actually spawns it's own thread (but calls the delegate functions on the thread that called initWithRequest). So basically, on the thread that calls initWithRequest you are actually done with the connection object and you can release it. All the while it's doing stuff on some other thread that you don't need to be concerned with.
Also I should note that if you do release it there, make sure you DON'T release it in the finish/fail methods, because it won't be a valid object.

What's wrong on following URLConnection?

See also:
Objective-C Asynchronous Web Request with Cookies
I spent a day writing this code and can anyone tell me what is wrong here?
WSHelper is inherited from NSObject, I even tried NSDocument and NSObjectController and everything..
-(void) loadUrl: (NSString*) urlStr{
url = [[NSURL alloc] initWithString:urlStr];
request = [NSURLRequest requestWithURL:url cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
if(connection)
{
receivedData = [[NSMutableData data] retain];
//[connection start];
}
else
{ display error etc... }
NSApplication * app = [NSApplication sharedApplication];
[app runModalForWindow: waitWindow];// <-- this is the problem...
}
-(void)connection: (NSURLConnection*)connection didReceiveData:(NSData*)data{
progressText = #"Receiving Data...";
[receivedData appendData:data];
}
-(void)connection: (NSURLConnection *)connection didFailWithError:(NSError *)error{
progressText = #"Error...";
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText:[error localizedDescription]];
[alert runModal];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
progressText = #"Done...";
pData = [[NSData alloc] initWithData:receivedData];
[self hideWindow];
}
The code just wont do anything, it doesnt progress at all. I even tried it with/without startImmediately:YES but no luck !!!, this is executed in main window so even the thread and its run loop is running successfully.
I tried calling synchronous request, and it is working correctly !! But I need async solution.
I have added CoreServices.Framework in project, is there anything more I should be adding to the project? any compiler settings? Or do i have to initialize anything before I can use NSURLConnection?
Any solution to run NSURLConnection on different thread on its own NSRunLoop, Objective-C and MAC Development has no sample code anywhere in documentation that makes everything so difficult to code.
I also met the same problem that didn't get the delegate method called when using NSURLConnection in a Modal Window.
after some investigation, following code resolve it.
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:NO];
[conn scheduleRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
[conn start];
However, when connectionDidFinishLoading called, [NSApp stopModal] doesn't work, need call [NSApp abortModal] instead.
Firstly you're making starting the connection too complicated. Change to:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]
Remove [connection start]. Now:
Is your app definitely running the run loop normally? NSURLConnection requires this to work.
Are you able to perform a synchronous load of the URL request?
In the debugger, can you see that url is what you expect it to be? What is it?
Is it possible that you're deallocating WSHelper before any delegate messages are received? NSURLConnection is asynchoronous after all.
One does not need to do anything special to use NSURLConnection, it's a straightforward part of the Foundation framework. No special compiler settings required. No initialization before use. Nothing. Please don't start blindly trying stuff like bringing in CoreServices.Framework.
As sending the request synchronously works, there must be something wrong with your handling of the asynchronous aspect. It could be:
The runloop is not running in NSDefaultRunLoopMode so the connection is unable to schedule itself.
Some other part of your code is calling -cancel on the connection before it has a chance to load.
You are managing to deallocate the connection before it has a chance to load.
Real problem
Ah, in fact I've just realised what's going on. You are calling:
-[NSApp runModalForWindow:]
Read the description of what this method does. It's not running the run loop like NSURLConnection expects. I'd say that really, you don't want to be presenting a window quite like this while running a URL connection for it.
I'd also suggest that you implement the -connection:didReceiveResponse: delegate method too. You want to check here that the server is returning the expected status code.
You say that you're using this in a modal dialog? A modal dialog puts the run loop into a different mode. You should be able to get this to work by scheduling it to run in the modal dialog run loop mode, in addition to the normal run loop mode. Try adding this line of code after you allocate connection in loadURL:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
Hope that helps.
How do you know it isn't doing anything? Are there any error or warning messages during the compile? Are any error messages showing up on console when the program is running?
Have you tries setting breakpoints in your code and following through what you expect to be happening?

Cocoa: NSURLConnection not attempting an HTTP Request

I've had significant success with NSURL, NSURL[Mutable]Request, NSURLConnection with my iPhone applications. When trying to compile a stand alone Cocoa application, 10 line program to make a simple HTTP request, there are zero compiler errors or warnings. The program compiles fine, yet the HTTP Request is never made to my web server (I'm running a tcpdump and watching Apache logs in parallel). When I run very similar code in an iPhone app, essentially copy/pasted as evil as that is, all works golden.
I kept the code for the 'obj' declaration in the delegate to NSURLConnection out of this code snippet for the sake of simplicity. I'm also passing the following to gcc:
gcc -o foo foo.m -lobjc -framework cocoa
Thanks for any insight.
#import <Cocoa/Cocoa.h>
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString * urlstr = #"http://tmp/test.php";
[NSApplication sharedApplication];
NSObject *obj = [[NSObject alloc] init];
NSURL *url = [NSURL URLWithString: urlstr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
if([request isKindOfClass:[NSMutableURLRequest class]])
NSLog(#"request is of type NSMutableURLRequest");
[request setHTTPMethod:#"GET"];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:obj
startImmediately:YES];
if(connection)
NSLog(#"We do have a connection.");
[pool release];
return 0;
}
The other poster pretty much answered this for you, but I thought I would just add a few things.
First, you don't really need to link to Cocoa for this, just linking to the Foundation framework is okay. Also, since you don't need a connection to the Window Server, you can get rid of the [NSApplication sharedApplicaiton] call. If you want just a simple, console test application to start with, use what you have now and add this before your [pool realease] call:
[[NSRunLoop currentRunLoop] run];
Please note, however, that this will block and may actually never return. Before calling this, you can add a timer if you want your code to actually do something in the background :) See the documentation on NSRunLoop for more ways to use this.
NSURLConnection is an asynchronous API that relies upon NSRunLoop. Your posted code never creates a run loop for the connection to run in. Therefore, I presume Cocoa is unable to create the connection and so returns nil. Things to look into:
1) Anything in the console? Is NSURLConnection throwing an exception or logging an error?
2) What happens if you use the synchronous API instead? +[NSURLConnection sendSynchronousRequest:returningResponse:error:]
3) What is the point of this code? Cocoa is not designed for running directly from a main() function yourself. Is there a particular reason why you are not using the Xcode-provided application templates that will take care of setting up run loop, autorelease pool etc.?