views:

265

answers:

4

Hi guys. Basically what's happening is that I need to download a whole bunch of files in my app and I've set up a queue of sorts that downloads each file with an NSURLConnection and stores the server response incrementally in an NSMutableData until the download is finished and then writes the whole thing to disk.

Here's the relevant parts:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)_response {
    response = [_response retain];
    if([response expectedContentLength] < 1) {
        data = [[NSMutableData alloc] init];
    }
    else {
        data = [[NSMutableData dataWithCapacity:[response expectedContentLength]] retain];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)_data {
    [data appendData:_data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"saved: %@", self.savePath);
    [data writeToFile:self.savePath atomically:YES];
}

Any insights as to why this would be awfully slow? It's pretty bad with the Simulator and gets even worse on an actual device. My maximum download size is around 2 megabytes, so I figured storing the whole thing in memory until it finishes wouldn't be that bad of an idea. This gets up to about 20KB/s at best (with a direct ad-hoc wifi connection).

Edit: in all my test cases I do get a Content-Length header, so it's not a matter of growing the NSMutableData with each bit of response received.

Edit 2: this is all Shark gives me.

Edit 3: So this is how I set up the connection

NSMutableURLRequest *request = [[NSMutableURLRequest requestWithURL:[NSURL URLWithString:[@"http://xxx.xxx.xxx.xxx/index.php?service=" stringByAppendingString:service]]] retain];

[request setHTTPMethod:@"POST"];
[request setHTTPBody:[[options JSONRepresentation] dataUsingEncoding:NSUTF8StringEncoding]];

NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
[conn start];

Of course I don't actually have a hardcoded url and both request and conn are instance variables of the downloader class. Not that it should matter, but for JSON I'm using http://code.google.com/p/json-framework/. Options and service are method parameters (NSString and NSDictionary), not that those should matter either.

A: 

I would profile to find out where the slow down is occurring and in what pattern. Put a log statement in connection:didReceiveData to see record how often it is called. You're looking for:

  1. The relative elapsed time between calls to the method.
  2. Whether the time between calls increases as the app runs.

If the elapsed time between calls is where the app spends most of its time then the bottleneck is in the request itself. Either the request is misconfigured of the server is not sending quickly.

If the time between calls increases the longer the app runs, then it is probably a memory issue. As the data grows larger and the memory more constrained, the app has to swap more stuff in and out of memory which slows everything down. To test, log the various didReciveMemoryWarning methods in any active objects.


Update:

According to Shark, the problem is in your URL request and not the code you posted. You need to look at how you set up the request.

TechZen
Aren't memory warnings logged globally by default? I didn't log the time between between connection:didReceiveData, but I did tail my apache log and the time between individual requests is uniform and my total memory usage never goes beyond a couple of megabytes per download and each downloader object gets properly released after completion. I also did a time profile with Shark and most of the time is spent in in urlconnection related system calls. The server isn't the problem in this case, already tested that separately.
Joonas Trussmann
In the future, if you have information from tools like Shark and you control the server, you should include that info in the original question. Otherwise, we will assume you don't have that info and/or don't know how to obtain it.
TechZen
I updated my question with the code I use to set up my request. Thanks for your tips so far.
Joonas Trussmann
I don't see an issue with the posted code.
TechZen
A: 

Boy this is embarrassing. Turns out my Content-Length header was inaccurate, which resulted in NSURLConnection needing to wait for some sort of timeout before it would finish, even though it had all the data. Makes sense really. Maybe this will help someone else out.

Joonas Trussmann
A: 

Joonas, let me know your correct Content-Legth, it will help us regards and thank you for your great information Fredy

freddy
A: 

Joonas, let me know your correct Content-Legth, it will help us regards and thank you for your great information Fredy

freddy
The correct content length is the lengths, in bytes, of your data. So you really need to do this on the server side.
Joonas Trussmann