Running NSURLConnection from within an NSOperation?
-
I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
-Laurent.
--
Laurent Daudelin
AIM/iChat/Skype:LaurentDaudelin http://nemesys.dyndns.org
Logiciels Nemesys Software <laurent.daudelin...>
Photo Gallery Store: http://laurentdaudelin.shutterbugstorefront.com/g/galleries -
> I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
Try using the synchronous version of NSURLConnection since you are launching it from within a NSOperation already-
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse**)response error:(NSError **)error
The docs mention that no special run loop configuration is necessary in this case.
Greg -
On Feb 9, 2010, at 14:10, Greg Reichow wrote:
>
>> I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
>
> Try using the synchronous version of NSURLConnection since you are launching it from within a NSOperation already-
>
> + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse**)response error:(NSError **)error
>
>
> The docs mention that no special run loop configuration is necessary in this case.
>
> Greg
Thanks, Greg!
-Laurent.
--
Laurent Daudelin
AIM/iChat/Skype:LaurentDaudelin http://nemesys.dyndns.org
Logiciels Nemesys Software <laurent.daudelin...>
Photo Gallery Store: http://laurentdaudelin.shutterbugstorefront.com/g/galleries -
On 9 Feb 2010, at 22:18, Laurent Daudelin wrote:
>> Try using the synchronous version of NSURLConnection […]
No, don't do this. This method simply creates a private NSURLConnection delegate and enters the run loop until completion or failure.
This ties the NSOperation worker thread up until completion which may shift thread ramp up costs onto other parts of you application.
Instead you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
Keith -
On Feb 9, 2010, at 15:01, Keith Duncan wrote:
>
> On 9 Feb 2010, at 22:18, Laurent Daudelin wrote:
>
>>> Try using the synchronous version of NSURLConnection […]
>
> No, don't do this. This method simply creates a private NSURLConnection delegate and enters the run loop until completion or failure.
>
> This ties the NSOperation worker thread up until completion which may shift thread ramp up costs onto other parts of you application.
>
> Instead you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
>
> Keith
Thanks, Keith. I'll look into it.
-Laurent.
--
Laurent Daudelin
AIM/iChat/Skype:LaurentDaudelin http://nemesys.dyndns.org
Logiciels Nemesys Software <laurent.daudelin...>
Photo Gallery Store: http://laurentdaudelin.shutterbugstorefront.com/g/galleries -
On 2010 Feb 09, at 14:10, Greg Reichow wrote:
>> I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
>
> Try using the synchronous version of NSURLConnection since you are launching it from within a NSOperation already-
>
> + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse**)response error:(NSError **)error
Indeed sendSynchronousRequest: will work for a quick hack to talk to, say, your own server, but if you hope to some day have a real app which predictably handles real-world corner cases, well, let me quote what was told to me once:
The NSErrors that you get from -sendSynchronousRequest: are not documented. For example, if you give it a wrong username/password combination, you get NSURLErrorUserCancelledAuthentication = -1012. Apparently this is an implementation detail, that when it receives an authentication challenge, it cancels. You're leaving the policy decisions to Apple instead of making them yourself, and since they are not documented, Apple may change them at any time.
It is true that the alternative asynchronous methods require a run loop. (Don't use a timer). -
>>> I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
>>
>> Try using the synchronous version of NSURLConnection since you are launching it from within a NSOperation already-
>>
>> + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse**)response error:(NSError **)error
>
> Indeed sendSynchronousRequest: will work for a quick hack to talk to, say, your own server, but if you hope to some day have a real app which predictably handles real-world corner cases, well, let me quote what was told to me once:
>
> The NSErrors that you get from -sendSynchronousRequest: are not documented. For example, if you give it a wrong username/password combination, you get NSURLErrorUserCancelledAuthentication = -1012. Apparently this is an implementation detail, that when it receives an authentication challenge, it cancels. You're leaving the policy decisions to Apple instead of making them yourself, and since they are not documented, Apple may change them at any time.
Ahh yes, the things you learn by posting a bad answer.. thanks for the additional insight.
Greg -
On 2010 Feb 10, at 01:43, Greg Reichow wrote:
> the things you learn by posting a bad answer.
-sendSynchronousRequest: is not necessarily a bad answer, Greg. In some situations it is adequate, and then it is the correct choice due to its simplicity. -
There is at least one iPhone example that users the URL Loading System with NSOperation.
On Feb 9, 2010, at 4:12 PM, Laurent Daudelin wrote:
> I'm trying to run an NSURLConnection from an NSOperation. Apparently, it won't run. I know that NSURLConnection need a run loop. Does that mean I'll have to setup some kind of NSTime in my NSOperation and then call my run loop at regular intervals?
>
>
Search the docs for (and I’m not being a smart *** here) “Lazy”
It loads a document, and then loads the contents specified by that document using NSOperation -
On Feb 9, 2010, at 3:01 PM, Keith Duncan wrote:
> ...you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
I had a working async NSURLConnection scheme where I initialized the connection in a new thread and pumped its run loop manually on that thread until I saw the delegate set a completion flag. I thought I'd convert it to blocks since it cleans up some ugly code there. I'm 95% of the way there and things look good, but there are some lingering problems in my code that I don't understand. The above tip helped with one of my problems: the automatic block thread didn't pump the NSURLConnection run loop, so I scheduled the connection in the main run loop like Keith suggested and things worked. My first question is: doesn't this block the main loop with io? I have another version which uses a block to manually pump, but then this ties up an automatic block thread, which are supposed to be short-lived. Is it really a good practice to drive the NSURLConnection from the main loop?
My second question is: How do I memory manage the autorelease pool that the delegate uses? Here's a rough idea of what I have now:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
__block NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init];
NSURLConnection *connection = [NSURLConnection alloc];
ResponseHandler *handler = [ResponseHandler alloc];
[handler initWithDelegate:delegate
completionBlock:^{dispatch_async(queue, ^{
[connection release];
[handler release];
[autoReleasePool release];
[delegate release];
});}];
[connection initWithRequest:urlRequest delegate:handler startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
});
ResponseHandler is pretty simple. It processes the data and calls the completionBlock when done. With that code I get "attempt to pop an unknown autorelease pool". When I drop the release altogether the error goes away, but that seems like a leak.
John -
NSURLConnection already does its work asynchronously, there is no need to run it on a separate thread. However if you still want to, you should use [NSRunLoop currentRunLoop], which returns the run loop associated with that thread.
In your code you are not creating your objects properly. You should use the form [[Class alloc] init] then operate on that object:
> NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:handler startImmediately:NO];
Also I don't believe you need to create the autorelease pool. I think GCD does it for you.
Lastly, you should run the thread's run loop until your delegate method gets called indicating the NSURLConnection is done.
On Jun 20, 2010, at 1:42 PM, John Heitmann wrote:
>
> On Feb 9, 2010, at 3:01 PM, Keith Duncan wrote:
>
>> ...you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
>
> I had a working async NSURLConnection scheme where I initialized the connection in a new thread and pumped its run loop manually on that thread until I saw the delegate set a completion flag. I thought I'd convert it to blocks since it cleans up some ugly code there. I'm 95% of the way there and things look good, but there are some lingering problems in my code that I don't understand. The above tip helped with one of my problems: the automatic block thread didn't pump the NSURLConnection run loop, so I scheduled the connection in the main run loop like Keith suggested and things worked. My first question is: doesn't this block the main loop with io? I have another version which uses a block to manually pump, but then this ties up an automatic block thread, which are supposed to be short-lived. Is it really a good practice to drive the NSURLConnection from the main loop?
>
> My second question is: How do I memory manage the autorelease pool that the delegate uses? Here's a rough idea of what I have now:
>
> dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
> dispatch_async(queue, ^{
> __block NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init];
> NSURLConnection *connection = [NSURLConnection alloc];
> ResponseHandler *handler = [ResponseHandler alloc];
>
> [handler initWithDelegate:delegate
> completionBlock:^{dispatch_async(queue, ^{
> [connection release];
> [handler release];
> [autoReleasePool release];
> [delegate release];
> });}];
>
> [connection initWithRequest:urlRequest delegate:handler startImmediately:NO];
> [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
> [connection start];
> });
>
> ResponseHandler is pretty simple. It processes the data and calls the completionBlock when done. With that code I get "attempt to pop an unknown autorelease pool". When I drop the release altogether the error goes away, but that seems like a leak.
>
> John
-
Thanks for the response. There is definitely a lot of goofiness in that code. I started from the apple xml performance demo, which had run-loop code that led me to believe that I had to run the NSURLConnection run loop in a thread. It's great that that's not the case (I re-read the example[1] and the only reason they have it is to do some blocking). I can drop all block use except the completion block, which should be a really nice cleanup.
I'll play around with the autorelease pool. The apple example had manual autorelease pool management during parsing. I'm going to just drop that entirely since I don't think my app is _that_ performance sensitive.
The odd init-at-a-distance code was because I originally had the connection start upon init, which meant that I had a chicken and egg problem wrt initializing the handler (with a completion block that needed the connection) and the connection (which needed the handler). Now that start is manually run I can clean that up too. Thanks again,
John
[1] http://developer.apple.com/iphone/library/samplecode/XMLPerformance/Listing
s/Classes_LibXMLParser_m.html#//apple_ref/doc/uid/DTS40008094-Classes_LibXM
LParser_m-DontLinkElementID_10
On Jun 21, 2010, at 10:48 AM, Kevin Wojniak wrote:
> NSURLConnection already does its work asynchronously, there is no need to run it on a separate thread. However if you still want to, you should use [NSRunLoop currentRunLoop], which returns the run loop associated with that thread.
>
> In your code you are not creating your objects properly. You should use the form [[Class alloc] init] then operate on that object:
>
>> NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:handler startImmediately:NO];
>
>
> Also I don't believe you need to create the autorelease pool. I think GCD does it for you.
>
> Lastly, you should run the thread's run loop until your delegate method gets called indicating the NSURLConnection is done.
>
>
>
> On Jun 20, 2010, at 1:42 PM, John Heitmann wrote:
>
>>
>> On Feb 9, 2010, at 3:01 PM, Keith Duncan wrote:
>>
>>> ...you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
>>
>> I had a working async NSURLConnection scheme where I initialized the connection in a new thread and pumped its run loop manually on that thread until I saw the delegate set a completion flag. I thought I'd convert it to blocks since it cleans up some ugly code there. I'm 95% of the way there and things look good, but there are some lingering problems in my code that I don't understand. The above tip helped with one of my problems: the automatic block thread didn't pump the NSURLConnection run loop, so I scheduled the connection in the main run loop like Keith suggested and things worked. My first question is: doesn't this block the main loop with io? I have another version which uses a block to manually pump, but then this ties up an automatic block thread, which are supposed to be short-lived. Is it really a good practice to drive the NSURLConnection from the main loop?
>>
>> My second question is: How do I memory manage the autorelease pool that the delegate uses? Here's a rough idea of what I have now:
>>
>> dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
>> dispatch_async(queue, ^{
>> __block NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init];
>> NSURLConnection *connection = [NSURLConnection alloc];
>> ResponseHandler *handler = [ResponseHandler alloc];
>>
>> [handler initWithDelegate:delegate
>> completionBlock:^{dispatch_async(queue, ^{
>> [connection release];
>> [handler release];
>> [autoReleasePool release];
>> [delegate release];
>> });}];
>>
>> [connection initWithRequest:urlRequest delegate:handler startImmediately:NO];
>> [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
>> [connection start];
>> });
>>
>> ResponseHandler is pretty simple. It processes the data and calls the completionBlock when done. With that code I get "attempt to pop an unknown autorelease pool". When I drop the release altogether the error goes away, but that seems like a leak.
>>
>> John
>
-
> Lastly, you should run the thread's run loop until your delegate method gets called indicating the NSURLConnection is done.
Don't do that. That was discussed in the previous thread (search the archives). It irresponsibly consumes a workgroup thread.
Keith -
On 20 Jun 2010, at 21:42, John Heitmann wrote:
>
> On Feb 9, 2010, at 3:01 PM, Keith Duncan wrote:
>
>> ...you should create a 'concurrent' NSOperation as described in the documentation, and schedule your NSURLConnection on +[NSRunLoop mainRunLoop]. This will allow your NSOperation -start method to exit immediately and the thread to return to the pool.
>
> My first question is: doesn't this block the main loop with io?
Nope, it places the file descriptor for your socket into a select/kqueue loop somewhere that the system manages.
> With that code I get "attempt to pop an unknown autorelease pool". When I drop the release altogether the error goes away, but that seems like a leak.
Fixed version below:
> I have another version which uses a block to manually pump, but then this ties up an automatic block thread, which are supposed to be short-lived. Is it really a good practice to drive the NSURLConnection from the main loop?
>
> dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
> dispatch_async(queue, ^ {
> NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
>
> ResponseHandler *handler = [[ResponseHandler alloc] init];
> [handler initWithDelegate:delegate completionBlock:^ {
> dispatch_async(queue, ^ {
> NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
>
> […]
>
> [innerPool release];
> });
> }];
>
> NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:handler startImmediately:NO];
> [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
> [connection start];
>
> [outerPool release];
> });
Your autorelease pools are in different scopes. Autorelease pools are per-thread state and you need to return it to the default state (blank) at the end of your blocks. Your blocks are being executed on different threads.
Keith -
> I had a chicken and egg problem wrt initializing the handler (with a completion block that needed the connection) and the connection (which needed the handler).
In that circumstance you can decorate the variable that you need to reference inside the block as __block, and set it to nil. These variables aren't const copied, they are moved to the heap and referenced. When you assign to it in future lines, the block will reference the updated value.
Keith


