Re: asynchronous nsurlconnection in nsoperation
Re: asynchronous nsurlconnection in nsoperation
- Subject: Re: asynchronous nsurlconnection in nsoperation
- From: Andreas Grosam <email@hidden>
- Date: Thu, 22 Mar 2012 14:10:16 +0100
On Mar 21, 2012, at 4:59 PM, Ariel Feinerman wrote:
> Hi,
> I wish to insert an asynchronous NSURLConnection into non-concurrent
> NSOperation
> the reason is to allow necessarily unarchive actions on the streaming date
> Is this for iOS or Mac OS?
> This for iOS
>
> The date is very large their amount is 400 Mb so the asynchronous is
> necessarily
> one cannot be in memory at once
>
> - (void) connection: (NSURLConnection *) connection didReceiveData:
> (NSData*) data {
> // Append the new data to receivedData.
> [_receivedData appendData: data];
> if ([_receivedData length] >= MAX_CHUNK) {
> // unarchive
> // write to file
> // [receivedData setLength: 0];
> }
>
> }
>
> Is there a correct way to do ?
Yes.
And your described approach works fine - if your connection delegates run on a secondary thread. Not sure, if you are actually required to use NSOperation, but basically, when you start your connection on a secondary thread (in order to keep the main thread responsive), it should work fine. (See the code below, how one can accomplish to start a connection on a secondary thread.)
Though, your approach is not optimal and it also **requires** that you can actually process (unarchive) **partial** data.
If you cannot process partial data, you have to search for a more elaborated solution to this problem:
There are several approaches, from simple to more complex. And, there are approaches which look promising but won't work!
What you need to use in any case is an **asynchronous** (NSURL)connection. Don't use a synchronously scheduled NSURLConnection with that amount of data!
1) The simplest one that works is to immediately write the received data to a temporary file (appending partial data to a file works fine). Then when finished downloading, process the temporary file, possibly leveraging mmap, NSStream, GCD or NSOperation. For this approach, you don't need anything special in the NSURLConnection. Starting it from the main thread is sufficient. Writing to the temp file is usually fast enough to keep the main thread responsive.
When you are processing the data, you can schedule the task on a secondary thread.
This approach is simple to implement, but is suboptimal performance wise - especially on devices with more than one CPU.
2) An approach that combines high performance with low memory foot-print is one that is a bit more elaborated. This would also require to schedule the connection on a secondary thread. (example on request)
3) A few approaches that WONT work and will likely eventually crash are the following:
3.a)
- (void) connection:(NSURLConnection*)connection didReceiveData:(NSData*) data
{
dispatch_async(queue, ^{processData:data;}];
}
where processData: is supposed to be able to handle partial data.
This will likely crash due to memory running out: If downloading is fast, and processing is slow, GCD queues a lot of buffers - until memory runs out.
3.b)
- (void) connection:(NSURLConnection*)connection didReceiveData:(NSData*) data
{
[_receivedData appendData: data];
}
And when finished, processing _receivedData.
This will crash due to memory running out.
Regards
Andreas
======================================================
You can start a connection in a secondary thread, as follows:
Anywhere, for instance a ViewController handling the "Start" Button:
// start the NSURLConnection in a secondary thread:
[NSThread detachNewThreadSelector:@selector(startConnectionInSecondaryThread)
toTarget:self withObject:nil];
And -startConnectionInSecondaryThread is implemented:
static NSString* kDownloadConnectionRunLoopMode = @"MyViewControllerDownloadConnectionRunMode";
- (void) startConnectionInSecondaryThread
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[self startConnection];
runLoopDone_ = NO;
// Enable the run loop:
// first, add a dummy source in order to prevent the Run loop from exiting
// immediately when the connection closes :
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:kDownloadConnectionRunLoopMode];
do {
BOOL processedSource = [[NSRunLoop currentRunLoop] runMode:kDownloadConnectionRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!runLoopDone_);
[pool release];
}
And finally, -startConnection, which is shown in more detail, to show some things you need to care about:
- (void) startConnection
{
// Note: startConnection can be performed on secondary threads, thus we need
// to schedule UIKit methods onto the main thread.
NSString* urlString = @"http://exmample.com";
NSURL* url = [NSURL URLWithString:urlString];
// Possibly configure/clear URL cache
NSTimeInterval request_timeout = 60.0;
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:request_timeout];
[request setValue:@"gzip,deflate" forHTTPHeaderField:@"Accept-Encoding"];
NSLog(@"Request header: %@", [request allHTTPHeaderFields]);
// Create the URL connection and set its delegate:
NSURLConnection* tmp = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
self.connection = tmp;
[tmp release];
if (connection_ == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleError:makeError(@"NSURLConnectionDownloadViewController", 3, @"Failure to create URL connection.")];
self.messageLabel.text = @"creating connection failed";
self.startDownloadButton.enabled = NO;
});
return;
}
NSLog(@"Start downloading %@", urlString);
dispatch_async(dispatch_get_main_queue(), ^{
self.messageLabel.text = @"start connection request";
// Start the status bar network activity indicator. We'll turn it off when
// the connection finishes or experiences an error.
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.startDownloadButton.enabled = NO;
self.cancelButton.enabled = YES;
});
// Schedule the connection's delegate methods in the current thread's run loop:
[connection_ scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: kDownloadConnectionRunLoopMode];
// Start the download
[self.connection start];
}
====================================================
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden